Store feed list as OPML

The old format consisted of |-separated lines, one per feed. OPML is the
usual standard for RSS feed lists, and snownews included the opml2snow
utility to convert to and from it. This commit converts urls format to
native OPML, removing the need for the utility. The file is also renamed
from urls to urls.opml.
This commit is contained in:
Mike Sharov 2021-04-11 17:36:45 -04:00
parent 6d776956ec
commit 62c0bd5e0e
17 changed files with 234 additions and 404 deletions

View File

@ -23,10 +23,6 @@ ${exe}: ${objs}
@echo "Linking $@ ..."
@${CC} ${ldflags} -o $@ $^ ${libs}
${exe}-static: ${srcs}
@echo "Statically linking $@ ..."
@${CC} ${cflags} ${ldflags} -s -static -o $@ $^
$O%.o: %.c
@echo " Compiling $< ..."
@${CC} ${cflags} -MMD -MT "$(<:.c=.s) $@" -o $@ -c $<
@ -45,8 +41,6 @@ ifdef bindir
exed := ${DESTDIR}${bindir}
exei := ${exed}/$(notdir ${exe})
o2si := ${exed}/opml2snow
s2oi := ${exed}/snow2opml
${exed}:
@echo "Creating $@ ..."
@ -54,20 +48,14 @@ ${exed}:
${exei}: ${exe} | ${exed}
@echo "Installing $@ ..."
@${INSTALL_PROGRAM} $< $@
${o2si}: opml2snow | ${exed}
@echo "Installing $@ ..."
@${INSTALL_PROGRAM} $< $@
${s2oi}: ${o2si} | ${exed}
@echo "Installing $@ ..."
@(cd ${exed}; rm -f $@; ln -s $(notdir $<) $(notdir $@))
installdirs: ${exed}
install: ${exei} ${o2si} ${s2oi}
install: ${exei}
uninstall: uninstall-bin
uninstall-bin:
@if [ -f ${exei} ]; then\
echo "Removing ${exei} ...";\
rm -f ${exei} ${o2si} ${s2oi};\
rm -f ${exei};\
fi
endif
@ -77,7 +65,7 @@ endif
clean:
@if [ -d ${builddir} ]; then\
rm -f ${exe} ${exe}-static ${objs} ${deps} $O.d;\
rm -f ${exe} ${objs} ${deps} $O.d;\
rmdir ${builddir};\
fi

12
conv.c
View File

@ -418,7 +418,7 @@ void CleanupString (char* string, int tidyness)
return;
size_t len = strlen (string);
while (len && (string[0] == '\n' || string[0] == ' ' || string[0] == '\t')) {
while (len && isspace (string[0])) {
// len=strlen(string) does not include \0 of string.
// But since we copy from *string+1 \0 gets included.
// Delicate code. Think twice before it ends in buffer overflows.
@ -426,12 +426,6 @@ void CleanupString (char* string, int tidyness)
--len;
}
// Remove trailing spaces.
while (len > 1 && string[len - 1] == ' ') {
string[len - 1] = 0;
--len;
}
// Eat newlines and tabs along the whole string.
for (size_t i = 0; i < len; ++i) {
if (string[i] == '\t')
@ -439,6 +433,10 @@ void CleanupString (char* string, int tidyness)
if (tidyness == 1 && string[i] == '\n')
string[i] = ' ';
}
// Remove trailing spaces.
while (len > 1 && isspace (string[len-1]))
string[--len] = 0;
}
// http://foo.bar/address.rdf -> http:__foo.bar_address.rdf

View File

@ -22,6 +22,7 @@
#include "uiutil.h"
#include "parse.h"
#include "setup.h"
#include "cat.h"
#include <ncurses.h>
#include <libxml/parser.h>
@ -178,6 +179,35 @@ int LoadAllFeeds (unsigned numfeeds)
return 0;
}
void AddFeed (const char* url, const char* cname, const char* categories, const char* filter)
{
struct feed* new_ptr = newFeedStruct();
new_ptr->feedurl = strdup (url);
if (strncasecmp (new_ptr->feedurl, "exec:", strlen ("exec:")) == 0)
new_ptr->execurl = true;
else if (strncasecmp (new_ptr->feedurl, "smartfeed:", strlen ("smartfeed:")) == 0)
new_ptr->smartfeed = true;
if (cname && cname[0])
new_ptr->custom_title = strdup (cname);
if (filter && filter[0])
new_ptr->perfeedfilter = strdup (filter);
if (categories && categories[0]) { // Put categories into cat struct.
char* catlist = strdup (categories);
for (char* catnext = catlist; catnext;)
FeedCategoryAdd (new_ptr, strsep (&catnext, ","));
free (catlist);
}
// Add to bottom of pointer chain.
if (!_feed_list)
_feed_list = new_ptr;
else {
new_ptr->prev = _feed_list;
while (new_ptr->prev->next)
new_ptr->prev = new_ptr->prev->next;
new_ptr->prev->next = new_ptr;
}
}
static void WriteFeedUrls (void)
{
if (!_feed_list_changed)
@ -185,7 +215,7 @@ static void WriteFeedUrls (void)
// Make a backup of urls.
char urlsfilename[PATH_MAX];
ConfigFilePath ("urls", urlsfilename, sizeof(urlsfilename));
ConfigFilePath ("urls.opml", urlsfilename, sizeof(urlsfilename));
// Write urls
FILE* urlfile = fopen (urlsfilename, "w");
@ -193,23 +223,51 @@ static void WriteFeedUrls (void)
syslog (LOG_ERR, "error saving urls: %s", strerror (errno));
return;
}
for (const struct feed * f = _feed_list; f; f = f->next) {
fputs (f->feedurl, urlfile);
fputc ('|', urlfile);
if (f->custom_title)
fputs (f->title, urlfile);
fputc ('|', urlfile);
for (const struct feedcategories * c = f->feedcategories; c; c = c->next) {
fputs (c->name, urlfile);
if (c->next) // Only add a colon of we run the loop again!
fputc (',', urlfile);
}
fputc ('|', urlfile);
if (f->perfeedfilter != NULL)
fputs (f->perfeedfilter, urlfile);
fputc ('\n', urlfile); // Add newline character.
// Write header
fputs (
"<?xml version=\"1.0\"?>\n"
"<opml version=\"2.0\"", urlfile);
// See if the custom namespace is needed
bool needNs = false;
for (const struct feed* f = _feed_list; f; f = f->next)
if (f->perfeedfilter)
needNs = true;
if (needNs)
fputs (" xmlns:snow=\"http://snownews.kcore.de/ns/1.0/\"", urlfile);
fputs (
">\n"
" <head>\n"
" <title>" SNOWNEWS_NAME " subscriptions</title>\n"
" </head>\n"
" <body>\n"
, urlfile);
// Write one outline element per feed
for (const struct feed* f = _feed_list; f; f = f->next) {
fputs ("\t<outline", urlfile);
if (f->custom_title)
fprintf (urlfile, " text=\"%s\"", f->custom_title);
if (f->feedurl)
fprintf (urlfile, " xmlUrl=\"%s\"", f->feedurl);
if (f->feedcategories) {
fputs (" category=\"", urlfile);
for (const struct feedcategories* c = f->feedcategories; c; c = c->next) {
fputs (c->name, urlfile);
if (c->next)
fputc (',', urlfile);
}
fputs ("\"", urlfile);
}
if (f->perfeedfilter)
fprintf (urlfile, " snow:filter=\"%s\"", f->perfeedfilter);
fputs ("/>\n", urlfile);
}
fputs (
" </body>\n"
"</opml>\n", urlfile);
fclose (urlfile);
_feed_list_changed = false;
}
@ -232,7 +290,12 @@ static void WriteFeedCache (const struct feed* feed)
return;
}
fputs ("<?xml version=\"1.0\" ?>\n\n<rdf:RDF\n xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n xmlns=\"http://purl.org/rss/1.0/\"\n xmlns:snow=\"http://snownews.kcore.de/ns/1.0/\">\n\n", cache);
fputs (
"<?xml version=\"1.0\" ?>\n\n"
"<rdf:RDF\n"
"\txmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
"\txmlns=\"http://purl.org/rss/1.0/\"\n"
"\txmlns:snow=\"http://snownews.kcore.de/ns/1.0/\">\n\n", cache);
if (feed->lastmodified)
fprintf (cache, "<snow:lastmodified>%ld</snow:lastmodified>\n", feed->lastmodified);

View File

@ -22,4 +22,5 @@ int UpdateFeed (struct feed* cur_ptr);
int UpdateAllFeeds (void);
int LoadFeed (struct feed* cur_ptr);
int LoadAllFeeds (unsigned numfeeds);
void AddFeed (const char* url, const char* cname, const char* categories, const char* filter);
void WriteCache (void);

1
main.h
View File

@ -29,7 +29,6 @@ struct feed {
char* link;
char* description;
char* lasterror;
char* content_type;
char* custom_title; // Custom feed title.
char* original; // Original feed title.
char* perfeedfilter; // Pipe feed through this program before parsing.

View File

@ -1,60 +0,0 @@
.\" Hey, EMACS: -*- nroff -*-
.\" First parameter, NAME, should be all caps
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
.\" other parameters are allowed: see man(7), man(1)
.TH opml2snow 1 "October 22, 2004"
.\" Please adjust this date whenever revising the manpage.
.\"
.\" Some roff macros, for reference:
.\" .nh disable hyphenation
.\" .hy enable hyphenation
.\" .ad l left justify
.\" .ad b justify to both left and right margins
.\" .nf disable filling
.\" .fi enable filling
.\" .br insert line break
.\" .sp <n> insert n+1 empty lines
.\" for manpage-specific macros, see man(7)
.SH NAME
opml2snow \- snownews OPML subsription file import/export utility
.SH SYNOPSIS
.B opml2snow
.RI [ options ] " file.opml " ...
.SH DESCRIPTION
This manual page documents briefly the
.B opml2snow
command.
This manual page was written for the Debian distribution
because the original program does not have a manual page.
.PP
.B opml2snow
is a PERL script that will convert an OPML
.RI ( "Outline Processor Markup Language" )
list of RSS subscriptions to the snownews format or export snownews'
internal subscription format to an OPML file.
It accepts input either on stdin, or as a list of files on the command\-line.
By default it will print to stdout, but if you want to append the output
to your subsription list instead, redirect the output with >> ~/.snownews/url
.SH OPTIONS
.TP
.B \-h, \-\-help
Show a few brief usage examples.
.TP
.B \--export [urls file]
Export snownews' subscription list to an OPML file. It reads from
~/.snownews/urls by default if you don't pass an optional file location.
.SH SEE ALSO
.BR snownews (1).
.PP
The OPML spec, available online at:
.RI < http://opml.scripting.com/spec >
.SH AUTHOR
This manual page was written by Joe Nahmias <jello@debian.org>,
for the Debian project (but may be used by others).

View File

@ -70,7 +70,7 @@ will open the link (usually the complete news text) in the browser.
The default browser is lynx, but you can change this by pressing
.B 'B'
in the main menu and entering a new default browser. Or you can edit
the file ~/.snownews/browser. The program replaces
the file ~/.config/snownews/browser. The program replaces
%s with the URL when expanding the string.
.P
You can rename a feed by pressing the key
@ -112,7 +112,7 @@ You can see all defined categories for a feed in the feed info.
.P
.B Customizing keybindings
.P
You can customize the keybindings by editing the file ~/.snownews/keybindings.
You can customize the keybindings by editing the file ~/.config/snownews/keybindings.
The format is "function description:key". Do not change the string
"function description". The single character behind the colon represents
the key the program will associate with the corresponding function. If
@ -122,7 +122,7 @@ the default settings will be used instead.
.B Colours
.P
If you prefer to see the world in colours you can enable (and configure) colour
support in Snownews. Edit the file ~/.snownews/colors. To globally enable
support in Snownews. Edit the file ~/.config/snownews/colors. To globally enable
colours in the program, set enabled to "1". To set a colour, use the colour
key value that is listed in the comment in that file. You can disable usage
for single items by using the value "-1".
@ -134,25 +134,9 @@ the text. Tags will be stripped alltogether and some common HTML entities
will be translated. By default only the five entities defined in XML
(&lt; (<), &gt; (>), &amp; (&), &quot; (") and &apos; (')) plus a default
setting included will be translated. You can influence this behaviour with
the definition file at ~/.snownews/html_entities. See the comments on top
the definition file at ~/.config/snownews/html_entities. See the comments on top
of the file for further details.
.P
.B Importing/exporting subscriptions from other programs:
.P
Snownews can import opml subscription files from other RSS readers into
its own format with the included program "opml2snow".
To convert an opml subscription file type "opml2snow MySubsriptions.opml"
with MySubscriptions.opml being the name of the file you want to convert.
The program will print the converted data to stdout. Use
"opml2snow file.opml >converted" to put the converted data into the file
"converted" or "opml2snow file.opml >>~/.snownews/urls"
to append it to snownews' subscription list.
.P
You can also export snownews' internal format to an OPML file with
"opml2snow --export".
.P
See "opml2snow -h" or its manpage for more usage examples.
.P
.B HTTP client features
.P
Snownews' HTTP client will follow HTTP server redirects. If the URL you have
@ -168,7 +152,7 @@ To subscribe to a feed that requires authentication, use an URL
.B http://username:password@server/feed.rss.
You can use cookies to supply log in information to a webserver. Put the
cookies you want Snownews to use into the file
.B ~/.snownews/cookies.
.B ~/.config/snownews/cookies.
The file has to be in standard Netscape cookies.txt file format. Mozilla uses
this format for example. Snownews will automatically send the right cookies
to the right webserver. You can also just place a symlink to your browser's
@ -242,11 +226,6 @@ the program crash, regardless what you're doing is a bug and needs to be fixed.
XML parsing errors are probably not fixable in Snownews since libxml is responsible
for parsing a document's XML. Though you can report problematic feeds anyway,
it may be a bug in Snownews.
.P
Please read http://kiza.kcore.de/software/snownews/faq#toc4 before you report a bug.
.SH SEE ALSO
.BR opml2snow (1).
.SH AUTHOR
Oliver Feiler <kiza@kcore.de>

View File

@ -77,7 +77,7 @@ Webbrowser.
Der Standardbrowser ist Lynx. Sie können dies aber ändern, indem
Sie im Hauptmenü die Taste
.B 'B'
drücken. Alternativ können Sie auch die Datei ~/.snownews/browser
drücken. Alternativ können Sie auch die Datei ~/.config/snownews/browser
editieren. Das Programm ersetzt %s durch die URL, wenn es den
Webbrowser aufruft.
.P
@ -127,7 +127,7 @@ der Feed Information.
.B Tatenbelegung konfigurieren
.P
Die Tastenbelegung können Sie ändern, indem Sie die Datei
~/.snownews/keybindings editieren. Das Format der Datei ist
~/.config/snownews/keybindings editieren. Das Format der Datei ist
"Funktionsbeschreibung:Taste". ändern Sie bitte nicht den Text der
Funktionsbeschreibung. Der einzelne Buchstabe hinter dem Doppelpunkt
stellt die Taste dar, die das Programm mit der entsprechenden Funktion
@ -143,19 +143,7 @@ entfernt und gebr
nur die in XML definierten Entities (&lt; (<), &gt; (>), &amp; (&), &quot;
(") and &apos; (')) und eine interne Standardvorgabe umgewandelt. Sie
können diese Liste erweitern, indem Sie die gewünschten Zeichen in die
Datei ~/.snownews/html_conversion eintragen.
.P
.B Importieren von Abonnements aus anderen Programmen:
.P
Snownews kann opml Abonnements von anderen RSS Newsreadern in sein
eigenes Format importieren. Dies funktioniert mit dem mitgelieferten
Programm "opml2snow". Um eine Subscription Datei zu konvertieren,
tippen Sie "opml2snow Datei.opml", wobei Datei.opml die zu importierende
Datei ist. Das Programm gibt die konvertierten Daten auf den Bildschirm
aus. Verwenden Sie "opml2snow Datei.opml >konvertiert" um die Ausgabe
in die Datei "konvertiert" zu schreiben, oder "opml2snow Datei.opml
>>~/.snownews/urls" um die konvertierten Daten zu der Feedliste von
Snownews hinzuzufügen. Geben Sie "opml2snow -h" ein für mehr Beispiele.
Datei ~/.config/snownews/html_conversion eintragen.
.P
.B Einige weitere nützliche Dinge:
.P

View File

@ -72,7 +72,7 @@ ouvre le lien dans un navigateur.
Le navigateur par défaut est lynx, mais ceci peut être configuré en pressant
.B Shift+B
dans le menu principal puis en entrant un nouveau navigateur.
Vous pouvez également éditer le fichier ~/.snownews/browser.
Vous pouvez également éditer le fichier ~/.config/snownews/browser.
Le programme remplace %s par l'URL dans la chaîne entrée.
.P
Une connexion peut être interrompue en pressant
@ -110,27 +110,13 @@ pressez simplement son num
.P
Vous pouvez voir toutes les catégories d'un feed dans son écran d'info.
.P
Vous pouvez modifier les touches en éditant le fichier ~/.snownews/keybindings.
Vous pouvez modifier les touches en éditant le fichier ~/.config/snownews/keybindings.
Le format est "description de la fonction:touche". Ne changez pas la partie
"description de la fonction". Le caractère suivant les deux points représente
la touche que le programme associera avec la fonction correspondante.
Si une définition est effacée, ou si le programme ne peut pas lire le fichier
pour quelque raison, alors le réglage par défaut sera utilisé.
.P
.B Import de feeds d'un autre programme:
.P
Snownews peut importer des fichiers d'abonnements RSS issus d'autres programmes
dans son propre format au moyen de l'outil "opml2snow".
Pour convertir un fichier d'abonnement de type opml, taper:
"opml2snow MesAbonnements.opml"
où MesAbonnements.opml est le nom du fichier à convertir.
opml2snow enverra le résultat de la conversion sur la sortie standard.
Taper "opml2snow fichier.opml > converti" pour écrire le résultat dans un
fichier appelé "converti", ou encore "opml2snow fichier.opml >> ~/.snownews/urls"
pour ajouter les abonnements convertis dans le fichier des abonnements de
Snownews.
Voir "opml2snow -h" pour d'autres exemples d'utilisation.
.P
.B Quelques détails supplémentaires:
.P
Lors de la visualisation de la description d'un élément, vous pouvez retourner

View File

@ -81,7 +81,7 @@ premendo il tasto
.B 'B'
nel menu principale e impostando un browser differente.
In alternativa, si pu modificare il file di configurazione
~/.snownews/browser. Il programma sostituisce alla stringa
~/.config/snownews/browser. Il programma sostituisce alla stringa
%s l'URL indicata dal link.
.P
Le connessioni ai siti possono essere interrotte premendo il tasto
@ -136,7 +136,7 @@ Si possono vedere tutte le categorie cui un feed fa parte tra le informazioni
relative ad un feed.
.P
Si possono personalizzare le associazioni dei tasti modificando il file
~/.snownews/keybindings.
~/.config/snownews/keybindings.
Il formato del file <20>semplicemente
"descrizione della funzione:key". Attenzione: non si deve modificare la stringa
"descrizione della funzione".
@ -146,22 +146,6 @@ Se si eliminano delle definizioni o se, per qualsiasi motivo, il programma
non <20>in grado di effettuare il parsing del file, verranno usate le
impostazioni predefinite.
.P
.B Importare sottoscrizioni da altri programmi.
.P
Snownews <20>in grado di importare file di sottoscrizioni opml da altri lettori
RSS usando il programma "opml2snow".
Per convertire un file opml nel formato usato da Snownews, <20>sufficiente digitare il comando
"opml2snow MySubsriptions.opml", dove
MySubscriptions.opml rappresenta il nome del file con le sottoscrizioni da
importare.
opml2snow stampa i dati convertiti sullo stdout.
Basta digitare
"opml2snow file.opml >converted" per salvare tali dati nel file
"converted", o "opml2snow file.opml >>~/.snownews/urls"
per appenderli alla lista dei feed sottoscritti da Snownews.
Per maggiori informazioni, si pu lanciare
"opml2snow -h".
.P
.B Alcuni particolari interessanti.
.P
Se si sta visualizzando la descrizione di un elemento, <20>possibile

View File

@ -16,7 +16,7 @@ Als u een feed selecteert en op Enter drukt zal het programma elk item van deze
Selecteer een item en druk op Enter om de beschrijving te lezen. Als er geen beschrijving aan toegevoegd is zal de tekst 'Geen beschrijving beschikbaar.' verschijnen. U kunt met de pijltjestoetsen links en rechts, respectievelijk 'p' en 'n' het vorige of volgende nieuwsitem uit de lijst selecteren. Door op 'o' te drukken wordt de link (meestal de complete nieuwstekst) geopend in de browser.
De standaardbrowser is lynx, maar u kunt dit veranderen door op 'B' te drukken in het hoofdmenu en een nieuwe standaardbrowser in te voeren. Of u kunt het bestand ~/.snownews/browser bewerken. Het programma vervangt %s door de URL bij het uitbreiden van de string.
De standaardbrowser is lynx, maar u kunt dit veranderen door op 'B' te drukken in het hoofdmenu en een nieuwe standaardbrowser in te voeren. Of u kunt het bestand ~/.config/snownews/browser bewerken. Het programma vervangt %s door de URL bij het uitbreiden van de string.
Netwerk verbindingen kunnen onderbroken worden door op
.B 'z'
@ -44,14 +44,7 @@ U kunt alle al gedefinieerde catgorie
.P
.B Toetsenbord instellen
.P
U kunt de toetsen veranderen door het bestand ~/.snownews/keybindings te bewerken. Het formaat is "functiebeschrijving:toets". Verander de "functiebeschrijving" niet. Het teken achter de dubbele punt is de toets die het programma aan die functie toewijst. Als u een definitie verwijdert of het programma kan het bestand, om de een of andere reden, niet lezen worden de standaard instellingen gebruikt.
.B Abbonnementen importeren uit ander programma's:
.P
Snownews kan opml abbonnementsbestanden importeren van andere RSS lezers naar het eigen formaat met het bijgevoegde programma "opml2snow".
Om een opml abbonnementsbestand te converteren type "ompl2snow MijnAbbonnementen.opml" waarbij MijnAbbonnementen.opml de bestandsnaam is van het bestand dat u wilt converteren.
Het programma toont de geconverteerde data op de stdout. Gebruik "ompl2snow bestand.opml > geconverteerd" om de geconverteerde data in het bestand "geconverteerd" te plaatsen of "ompl2snow bestand.opml >> ~/.snownews/url" om het toe te voegen aan het einde van de abbonnementenlijst van Snownews.
Gebruik "opml2snow -h" voor meer voorbeelden van het gebruik.
U kunt de toetsen veranderen door het bestand ~/.config/snownews/keybindings te bewerken. Het formaat is "functiebeschrijving:toets". Verander de "functiebeschrijving" niet. Het teken achter de dubbele punt is de toets die het programma aan die functie toewijst. Als u een definitie verwijdert of het programma kan het bestand, om de een of andere reden, niet lezen worden de standaard instellingen gebruikt.
.B Een paar dingen die het waard zijn uit te leggen:

View File

@ -67,7 +67,7 @@ Snownews -
Браузер по умолчанию - lynx, однако вы можете это изменить, нажав
.B 'B'
в основном меню (на основном экране), и ввести новую строку запуска
браузера по умолчанию. Либо можно отредактировать файл ~/.snownews/browser.
браузера по умолчанию. Либо можно отредактировать файл ~/.config/snownews/browser.
программы заменит %s соответствующим адресом при обработке этой строки.
(как организовать использование mozilla в качестве браузера для snownews,
смотрите в FAQ: http://home.kcore.de/~kiza/software/snownews/faq#toc2.1 )
@ -121,7 +121,7 @@ Snownews -
.B Переназначение клавиш
.P
Переопределить назначение клавиш можно, отредактировав файл
~/.snownews/keybindings. Формат записей - "описание функции:клавиша".
~/.config/snownews/keybindings. Формат записей - "описание функции:клавиша".
Строку "описание функции" изменять нельзя. Единичный символ после
двоеточия описывает клавишу, которую программа назначит соответствующей
функции. Если описание будет удалено из файла, или программа не сможет
@ -135,21 +135,9 @@ Snownews
HTML-коды конвертируются. По умолчанию конвертируются только пять кодов,
определенных в XML (&lt; (<), &gt; (>), &amp; (&), &quot; (") and &apos; ('))
плюс определенные в стандартных настройках snownews. Это можно
изменить в файле ~/.snownews/html_entities. Дальнейшие объяснения смотрите
изменить в файле ~/.config/snownews/html_entities. Дальнейшие объяснения смотрите
в комментариях в начале этого файла.
.P
.B Импортирование подписок из других программ:
.P
Snownews может импортировать файлы подписок opml из других RSS-просмотрщиков
в свой собственный формат с помощью включенной в поставку программы "opml2snow".
Конвертирование opml-подписки производится командой "opml2snow MySubsriptions.opml",
где MySubscriptions.opml - имя файла, который вы желаете конвертировать.
Программа выдает сконвертированные данные на стандартный вывод. Команда
"opml2snow file.opml >converted" поместит сконвертированные данные в файл
"converted", команда "opml2snow file.opml >>~/.snownews/urls" - добавит
их в список лент Snownews. Для примеров использования программы, запустите
"opml2snow -h".
.P
.B Некоторые стоящие упоминания вещи:
.P
Во время просмотра записи, для возвращения к предыдущему экрану
@ -177,7 +165,7 @@ Snownews
.B http://username:password@server/feed.rss.
Возможно также использовать cookies для передачи аутентификационной информации
на вебсервер. Snownews будет использовать cookies, которые вы поместите в файл
.B ~/.snownews/cookies.
.B ~/.config/snownews/cookies.
Файл должен быть в стандартном формате Netscape-овского cookies.txt. К примеру,
Mozilla использует этот формат. Snownews автоматически посылает нужные cookie
на нужный вебсервер. Возможно также просто создать символическую ссылку на
@ -235,8 +223,5 @@ Snownews
Пожалуйста, прочтите http://kiza.kcore.de/software/snownews/faq#toc4 перед тем,
как сообщать об ошибке.
.SH СМОТРИ ТАКЖЕ
.BR opml2snow (1).
.SH АВТОР
Oliver Feiler <kiza@kcore.de>

View File

@ -118,7 +118,7 @@ void DownloadFeed (const char* url, struct feed* fp)
// Check if failed because already up-to-date
long unmet = 0;
if (CURLE_OK == curl_easy_getinfo (curl, CURLINFO_CONDITION_UNMET, &unmet) && unmet) {
fp->lasterror = strdup (_("Feed already up to date"));
fp->lasterror = strdup (_("already up to date"));
UIStatus (fp->lasterror, 0, 0);
fp->problem = false;
} else {

146
opml2snow
View File

@ -1,146 +0,0 @@
#!/usr/bin/perl
#######################################################################
# $Id: opml2snow 1176 2007-12-02 10:55:52Z kiza $
#
# Snownews - A lightweight console RSS newsreader
#
# Copyright 2003-2004 Oliver Feiler <kiza@kcore.de>
# http://kiza.kcore.de/software/snownews/
#
# opml2snow
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#######################################################################
use strict;
use XML::LibXML;
use Data::Dumper;
use English;
#######################################################################
# Help
if (index($ARGV[0], "-h") >= 0) {
print "Snownews opml2snow - subsription file import/export utility\n\n".
"Creative usage examples:\n".
"Importing an OPML file:\n".
" opml2snow <list.opml >urls\n".
" opml2snow list.opml >urls\n".
" cat list.opml | opml2snow >urls\n\n".
"Exporting to OPML:\n".
" opml2snow --export [urls file] >MySubscriptions.opml\n".
" Alternatively you may run snow2opml [urls file] >file.opml\n\n".
"The program will print to stdout by default. If you want to append the output\n".
"to your subsription list, redirect to >>~/.snownews/urls instead.\n";
exit (0);
}
#######################################################################
# Importing
if (($PROGRAM_NAME =~ "snow2opml") || ($ARGV[0] eq "--export")) {
OPMLexport();
} else {
my $parser = XML::LibXML->new();
$parser->validation(0); # Turn off validation from libxml
$parser->recover(1); # And ignore any errors while parsing
my(@lines) = <>;
my($input) = join ("\n", @lines);
my($doc) = $parser->parse_string($input);
my($root) = $doc->documentElement();
# Parsing the document tree using xpath
my(@items) = $root->findnodes("//outline");
foreach (@items) {
my(@attrs) = $_->attributes();
foreach (@attrs) {
# Only print attribute xmlUrl=""
if ($_->nodeName =~ /xmlUrl/i) {
print $_->value."\n";
}
}
}
}
#######################################################################
# Exporting
sub OPMLexport {
my($inputfile);
if (defined($ARGV[1])) {
$inputfile = $ARGV[1];
} else {
$inputfile = "$ENV{'HOME'}/.snownews/urls"
}
open (URLS, "$inputfile");
my(@urls) = <URLS>;
close (URLS);
if (scalar(@urls) == 0) {
print "Could not read anything from \"$inputfile\"!\nExiting.\n";
exit(1);
}
# Create bare OPML doc
my($opml) = XML::LibXML::Document->new();
my($opml_root) = $opml->createElement('opml');
$opml->setDocumentElement($opml_root);
my($opml_comment) = $opml->createComment(' File generated by opml2snow ');
$opml_root->appendChild($opml_comment);
my($opml_head) = $opml->createElement('head');
$opml_root->appendChild($opml_head);
$opml_head->appendTextChild('title', 'MySubscriptions');
my($opml_body) = $opml->createElement('body');
$opml_root->appendChild($opml_body);
foreach(@urls) {
chomp;
/^(.*)?\|(.*?)\|(.*?)\|(.*)$/;
my($feed_url) = $1;
my($feed_url_hash) = $feed_url;
$feed_url_hash =~ tr/[\x00-\x19\x27-\x2a\x2f\x5b-\x60\x7b-\xff]/_/; # Translated from Hashify in conversions.c
$feed_url_hash = substr($feed_url_hash, 0, 128);
if ($feed_url_hash =~ /^smartfeed/) {
next;
}
my($feed_override_title) = $2;
my($feed_category) = $3;
my($feed_script) = 4;
my($parser) = XML::LibXML->new();
my($feed_doc) = $parser->parse_file("$ENV{'HOME'}/.snownews/cache/$feed_url_hash");
my($feed_root) = $feed_doc->documentElement();
$feed_root->setNamespace('http://purl.org/rss/1.0/', 'rss', 1);
# Make outline element. Actually I have absolutely no idea how
# this should look like. I copied the layout from an OPML file
# produced by NetNewsWire. The OPML "spec" is supposed to be at:
# http://www.opml.org/spec
my($opml_outline) = $opml->createElement('outline');
$opml_outline->setAttribute('text', $feed_root->findvalue('//rss:channel/rss:title'));
$opml_outline->setAttribute('title', $feed_root->findvalue('//rss:channel/rss:title'));
$opml_outline->setAttribute('description', $feed_root->findvalue('//rss:channel/rss:description'));
$opml_outline->setAttribute('type', 'rss');
$opml_outline->setAttribute('version', 'RSS');
$opml_outline->setAttribute('htmlUrl', $feed_root->findvalue('/rss:channel/rss:link'));
$opml_outline->setAttribute('xmlUrl', "$feed_url");
$opml_body->appendChild($opml_outline);
}
print $opml->serialize(1);
}

52
parse.c
View File

@ -17,6 +17,7 @@
// along with Snownews. If not, see http://www.gnu.org/licenses/.
#include "parse.h"
#include "feedio.h"
#include "conv.h"
#include <libxml/parser.h>
@ -466,3 +467,54 @@ int DeXML (struct feed* cur_ptr)
}
return 0;
}
unsigned ParseOPMLFile (const char* flbuf)
{
unsigned nfeeds = 0;
xmlDocPtr doc = xmlRecoverMemory (flbuf, strlen (flbuf));
if (!doc)
return nfeeds;
xmlNodePtr rootnode = xmlDocGetRootElement (doc);
if (!rootnode) {
xmlFreeDoc (doc);
return nfeeds;
}
if (xmlStrcmp (rootnode->name, (const xmlChar*) "opml") == 0) {
for (xmlNodePtr body = rootnode->children; body; body = body->next) {
if (body->type != XML_ELEMENT_NODE
|| 0 != xmlStrcmp (body->name, (const xmlChar*) "body"))
continue;
for (xmlNodePtr outline = body->children; outline; outline = outline->next) {
if (outline->type != XML_ELEMENT_NODE
|| 0 != xmlStrcmp (outline->name, (const xmlChar*) "outline"))
continue;
char* text = (char*) xmlGetProp (outline, (const xmlChar*) "text");
char* xmlUrl = (char*) xmlGetProp (outline, (const xmlChar*) "xmlUrl");
char* categories = (char*) xmlGetProp (outline, (const xmlChar*) "category");
char* filter = (char*) xmlGetNsProp (outline, (const xmlChar*) "filter", snowNs);
CleanupString (xmlUrl, 0);
CleanupString (text, 0);
CleanupString (categories, 0);
CleanupString (filter, 0);
if (xmlUrl && text) {
AddFeed (xmlUrl, text, categories, filter);
++nfeeds;
}
if (xmlUrl)
xmlFree (xmlUrl);
if (text)
xmlFree (text);
if (categories)
xmlFree (categories);
if (filter)
xmlFree (filter);
}
}
}
xmlFreeDoc (doc);
return nfeeds;
}

View File

@ -18,3 +18,4 @@
#include "main.h"
int DeXML (struct feed* cur_ptr);
unsigned ParseOPMLFile (const char* flbuf);

127
setup.c
View File

@ -21,6 +21,8 @@
#include "feedio.h"
#include "uiutil.h"
#include "conv.h"
#include "parse.h"
#include <ctype.h>
#include <ncurses.h>
void ConfigFilePath (const char* filename, char* path, size_t pathsz)
@ -51,18 +53,40 @@ void CacheFilePath (const char* filename, char* path, size_t pathsz)
path[pathlen-1] = 0;
}
static char* LoadFile (const char* filename)
{
struct stat st;
if (0 != stat (filename, &st) || !S_ISREG(st.st_mode) || !st.st_size)
return NULL;
int fd = open (filename, O_RDONLY);
if (fd < 0)
return NULL;
char* r = calloc (1, st.st_size);
for (ssize_t br = 0; br < st.st_size;) {
ssize_t ec = read (fd, &r[br], st.st_size-br);
if (ec <= 0) {
if (errno == EINTR)
continue;
free (r);
return NULL;
}
br += ec;
}
return r;
}
// Load browser command from ~./snownews/browser.
static void SetupBrowser (const char* filename)
{
char linebuf[128] = "lynx %s";
FILE* browserfile = fopen (filename, "r");
if (browserfile) {
fgets (linebuf, sizeof (linebuf), browserfile);
if (linebuf[strlen (linebuf) - 1] == '\n')
linebuf[strlen (linebuf) - 1] = '\0';
fclose (browserfile);
char* fbuf = LoadFile (filename);
if (!fbuf) {
_settings.browser = strdup ("lynx %s");
return;
}
_settings.browser = strdup (linebuf);
size_t fbuflen = strlen(fbuf);
while (isspace (fbuf[fbuflen-1]))
fbuf[--fbuflen] = 0;
_settings.browser = fbuf;
}
void SaveBrowserSetting (void)
@ -368,56 +392,40 @@ static void SetupEntities (const char* file)
}
}
static unsigned ParseOldFeedListFile (char* flbuf)
{
unsigned numfeeds = 0;
for (char* lineiter = flbuf; lineiter;) {
char* line = strsep (&lineiter, "\n");
// File format is url|custom name|comma seperated categories|filters
const char* url = strsep (&line, "|");
if (!url || !url[0])
continue; // no url
const char* cname = strsep (&line, "|");
const char* categories = strsep (&line, "|");
const char* filters = line;
AddFeed (url, cname, categories, filters);
++numfeeds;
}
return numfeeds;
}
static unsigned SetupFeedList (const char* filename)
{
unsigned numfeeds = 0;
FILE* configfile = fopen (filename, "r");
if (!configfile)
char* flbuf = LoadFile (filename);
if (!flbuf)
return numfeeds; // no feeds
while (!feof (configfile)) {
char linebuf[256];
if (!fgets (linebuf, sizeof (linebuf), configfile))
break;
if (linebuf[0] == '\n')
break;
linebuf[strlen (linebuf) - 1] = 0; // chop newline
// File format is url|custom name|comma seperated categories|filters
char* parse = linebuf;
const char* url = strsep (&parse, "|");
if (!url || !url[0])
continue; // no url
const char* cname = strsep (&parse, "|");
char* categories = strsep (&parse, "|");
const char* filters = parse;
++numfeeds;
struct feed* new_ptr = newFeedStruct();
new_ptr->feedurl = strdup (url);
if (strncasecmp (new_ptr->feedurl, "exec:", strlen ("exec:")) == 0)
new_ptr->execurl = true;
else if (strncasecmp (new_ptr->feedurl, "smartfeed:", strlen ("smartfeed:")) == 0)
new_ptr->smartfeed = true;
if (cname && cname[0])
new_ptr->custom_title = strdup (cname);
if (filters && filters[0])
new_ptr->perfeedfilter = strdup (filters);
if (categories && categories[0]) // Put categories into cat struct.
for (char *catnext = categories, *catname; (catname = strsep (&catnext, ","));)
FeedCategoryAdd (new_ptr, catname);
// Add to bottom of pointer chain.
if (!_feed_list)
_feed_list = new_ptr;
else {
new_ptr->prev = _feed_list;
while (new_ptr->prev->next)
new_ptr->prev = new_ptr->prev->next;
new_ptr->prev->next = new_ptr;
}
}
fclose (configfile);
// Check the format; old snownews style or OPML
if (0 != strncmp (flbuf, "<?xml", strlen("<?xml"))) {
numfeeds = ParseOldFeedListFile (flbuf);
_feed_list_changed = true; // force conversion to OPML
} else
numfeeds = ParseOPMLFile (flbuf);
free (flbuf);
return numfeeds;
}
@ -447,6 +455,17 @@ static void MigrateConfigToXDG (void)
if (0 != rename (dirname, configdir))
MainQuit ("Migrating configuration ~/.snownews to ~/.config/snownews", strerror (errno));
}
// Convert urls to urls.opml
char oldurls [PATH_MAX];
ConfigFilePath ("urls", oldurls, sizeof(oldurls));
if (0 == access (oldurls, F_OK)) {
char newurls [PATH_MAX];
ConfigFilePath ("urls.opml", newurls, sizeof(newurls));
if (0 != rename (oldurls, newurls))
MainQuit ("Migrating urls to urls.opml", strerror (errno));
_feed_list_changed = true; // force conversion to OPML
}
}
// Create snownews' config directories if they do not exist yet,
@ -494,6 +513,6 @@ unsigned Config (void)
ConfigFilePath ("html_entities", filename, sizeof(filename));
SetupEntities (filename);
ConfigFilePath ("urls", filename, sizeof(filename));
ConfigFilePath ("urls.opml", filename, sizeof(filename));
return SetupFeedList (filename);
}