parser

Написать ответ на текущее сообщение

 

 
   команды управления поиском

Класс для работы с RSS всех версий (включая dublin core, 2.0 и т.п.)

Sanja v.2 06.02.2004 16:37

Код того, что работает у меня на http://www.bougakov.com/blog/friends/
##############################
# В этот класс вынесен весь код, который
# отвечает за чтение RSS потоков с других сайтов.
@CLASS
rss

@USE
log.p

##############################
# Загрузчик
@load[]
$result[]

##############################
# Получение списка RSS ньюсфидов, которые не обновлялись больше часов, чем
# указано в столбце min_refresh_period и их обновление
@update_list[]

^if(def $form:limit){
	$limit[$form:limit]
	$limit(^limit.int(15))
}{$limit(15)}

<p>Идёт поиск до $limit RSS-лент, которые, согласно графику, уже должны быть обновлены&#133^;

^MAIN:dbconnect{
	$rss_feeds[
		^table::sql{SELECT
			uri,
			name,
			last_update,
			is_enabled,
			min_refresh_period,
			hours_to_keep
		FROM
			cityblog_rsslist
		WHERE
			is_enabled="yes"
		AND
			(UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_update)) >= (min_refresh_period * 60 * 60) # В часе 60 минут по 60 секунд, если вы не в курсе :-)
		ORDER BY
#			last_update
			RAND()
		}[$.limit($limit)]
	]

	^if(def $rss_feeds){
		<ol>
			^rss_feeds.menu{
				<li><a href="$rss_feeds.uri" target="_new">$rss_feeds.name</a>
				последний раз был открыт $rss_feeds.last_update,
				выполняется обновление&#133^;</li>
				^update_feed[$rss_feeds.uri]
	
				^void:sql{DELETE FROM cityblog_rssitems
					WHERE
						(UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(pubdate)) >= ($rss_feeds.hours_to_keep * 60 * 60)
					AND
						link = "$rss_feeds.uri"
					}
			}
		</ol>
	}{
		<p>В настоящий момент таковых нет.</p>
	}

}


##############################
# Повторное чтение RSS потока, разбор и запись в базу обновлений:
@update_feed[feeduri]

# пытаемся загрузить RSS файл с помощью ^file::load
# При этом может произойти целая туча неприятностей - другой сервер может не отвечать,
# файл окажется испорчен или удалён - на этот случай мы пользуемся оператором ^try

^try{
	$error(0)
	$rss[^file::load[text;^untaint{$feeduri};
		$.timeout(5)
		$.any-status(1)
		$.headers[
			$.User-Agent[CityBlog RSS aggregator from $MAIN:global_server]
		]
	]]

#	Поскольку парсер неспособен считать XML-элементы наподобие <item rdf:about="http://
#	выскребаем их из документа:
	
	$rss_replacements[^table::create{from	to
encoding="UTF-8"	encoding="windows-1251"
encoding="utf-8"	encoding="windows-1251"
encoding='utf-8'	encoding='windows-1251'
encoding="iso-8859-1"	encoding="windows-1251"
encoding='iso-8859-1'	encoding='windows-1251'
 rdf:	 rdf_
&mdash^;	-
&hellip^;	...
&nbsp^;
&quot^;	"
”	"
“	"
&copy^;	(c)
&laquo^;	"
&raquo^;	"
&trade^;	 <sup>(TM)</sup>
</rdf:RDF>	</rss>
xmlns=	xmln_dis=
xmlns:	xmln_dis_
<rdf:li	<rdf_li
<rdf:Seq	<rdf_Seq
</rdf:Seq	</rdf_Seq
rdf:resource=	rdf_resource=
<admin:generatorAgent	<admin_generatorAgent
<rdf:RDF	<rss version="2.0"
dc:subject>	subject>
dc:creator>	creator>
dc:publisher>	publisher>
syn:updateFrequency>	syn_updateFrequency>
syn:updatePeriod>	syn_updatePeriod>
syn:updateBase>	syn_updateBase>
sy:updateFrequency>	sy_updateFrequency>
sy:updatePeriod>	sy_updatePeriod>
sy:updateBase>	sy_updateBase>
dc:language>	language>
dc:rights>	rights>
dcterms:isReferencedBy	dcterms_isReferencedBy
admin:errorReportsTo	admin_errorReportsTo
<content:encoded>	<description>
</content:encoded>	</description>
trackback:ping	trackback_ping
trackback:about	trackback_about
dc:date>	pubDate>}]

	$rss_text[^untaint{$rss.text}]

#	Дочищаем текст:
	$rss_text[^rss_text.replace[$rss_replacements]]

#	Это - на случай особо извращённых интерпретаций RSS (с <rdf:Seq>):
	
	^if(^rss_text.match[rdf_Seq][]){

		$rdf_replacements[^table::create{from	to
</channel>
<items>
</items>
</rss>	</channel></rss>}]

		$rss_text[^rss_text.replace[$rdf_replacements]]
	}

	$xml[^xdoc::create{$rss_text}]
#	Избавляемся от корневого тега RDF
#	$xml[^xml.transform[/etc/classes/no_rdf.xsl]]
	$items[^xml.select[/rss/channel/item]]
#		<!-- ^taint[html][^rss_text.left(50000)] -->
}{
	$exception.handled(1)
	$result[
		<b>Не получилось загрузить RSS-поток!</b>
		^if(def $rss_text){
			<pre>^taint[html][^rss_text.left(1000)]</pre>
			^rss_text.save[/etc/temp/^taint[file-spec][^feeduri.match[/][g]{_}].xml]
		}{
		<!-- No RSS feed' text available! -->}
		<pre>^taint[html][$exception.comment]</pre>
	
	]
	$rss_text[ ]
	$error(1)
	^log:write[Не получилось загрузить RSS-поток $feeduri]
}

$updatedcnt(0)

^if(def $items){

#	Нам нужно узнать версию RSS-потока, который мы получили.
# 	Тонкость в том, что у RSS 2.0 есть тэг PubDate, а у RSS 0.91 нету.
#	Поэтому для RSS 2 мы пользуемся тем, что есть, а для старых версий RSS
#	пользуемся политикой "дата, когда выкачали = дата создания"
	^dates:load[]
	$rss_version[^xml.selectString[string(rss/@version)]]
	^for[i](1;$items){
		^if($rss_version eq "2.0"){
			$date{^xml.selectString[string(/rss/channel/item[position() = $i]/pubDate)]}
#			Вы думали, чехарда с датами закончилась? А нифига ;-)
#			Дата может быть в таком формате - Tue, 30 Sep 2003 14:32:46 MSD
#			или в таком - Mon, 29 Sep 2003 11:23:12 GMT. A есть ещё обозначение "UTC"...
#			see also:	http://208.30.42.17/logistics/tzhelp.asp
#						http://www.worldtimezone.com/wtz-names/wtz-msks.html
#			Нам надо привести это время к нашему локальному
			^if(def $date){
				$date[^date::create[^dates:string2GMT[$date]]]
			}{
				$date[^date::create[^dates:GMT_datetime[]]]
			}
		}{
#			Если мы имеем дело с RSS старой версии, берём текущую дату, приведённую к Гринвичу:
			$date[^date::create[^dates:GMT_datetime[]]]
		}
		$title{^xml.selectString[string(/rss/channel/item[position() = $i]/title)]}
		$link{^xml.selectString[string(/rss/channel/item[position() = $i]/link)]}
		$description{^xml.selectString[string(/rss/channel/item[position() = $i]/description)]}

		$countitems[^table::sql{SELECT
			COUNT(*)
		AS
			cnt
		FROM
			cityblog_rssitems
		WHERE
			link = "$link"
		}]

		$counter[$countitems.cnt]
		$counter(^counter.int(1))

		$rep[^table::create{from	to
'	\'
\	\\}]

	
		^if($counter == 0){
			^void:sql{INSERT INTO cityblog_rssitems (feed_id, title, link, description, pubdate)
				VALUES ('^feeduri.replace[$rep]', '^title.replace[$rep]', '^link.replace[$rep]', '^description.replace[$rep]', '^date.sql-string[]')
			}
	
			^updatedcnt.inc[]
		}{
#			Эта запись уже выкачивалась, апдейтить не надо.
		}

	}

# 	Обновляем запись об этом RSS-фиде в базе:
	^if(($updatedcnt >= 0) && ($error != 1)){
	^void:sql{UPDATE cityblog_rsslist
		SET
			last_update = NOW()
		WHERE
			uri = "$feeduri"
		}
	}
}

Найдено новых записей: <b>${updatedcnt}</b> </p>

# Обнуляем переменную:
$rss_text[]

##############################
# Вывод кешированных RSS-элементов из базы
@display[]

^MAIN:dbconnect{
	$rss_items[
		^table::sql{SELECT
			id,
			feed_id,
			title,
			link,
			description,
			DATE_FORMAT(pubdate, "%d/%m/%Y, %H:%i") as date
		FROM
			cityblog_rssitems
		ORDER BY
			pubdate DESC
		}[$.limit(20) $.offset(^if(def $form:skip){$form:skip}{0})]
	]

   $rss_sources[^hash::sql{select
			uri,
			name,
			last_update,
			is_enabled,
			min_refresh_period,
			hours_to_keep,
			allow_untaint
		FROM
			cityblog_rsslist
   }]
}

^if(def $rss_items){
		^rss_items.menu{
	
			$link[^untaint[as-is]{$rss_items.link}]
			$feed[^untaint[as-is]{$rss_items.feed_id}]
			$allow_untaint[$rss_sources.$feed.allow_untaint]
	
			<h4>
			^if(def ^untaint[as-is]{$rss_sources.$feed.uri}){
				<a href="^untaint[as-is]{$rss_sources.$feed.uri}">^untaint[as-is]{$rss_sources.$feed.name}</a>
			}{
				^untaint[as-is]{$rss_sources.$feed.name}
			}
				 -
			<a href="$link">^if(def $rss_items.title){^untaint[as-is]{$rss_items.title}}{Без заголовка}</a>
			<nobr>($rss_items.date GMT)</nobr></h4>
			^if($allow_untaint eq "yes"){
				$desc[^untaint[as-is]{$rss_items.description}]
			}{
				$desc[^untaint[html]{$rss_items.description}]
#				$desc[^desc.match[(^^|[^^="])(http://|ftp://|mailto:)([:a-z0-9~%{}._/?=&@,#-]+)(^^|[^^="])][gi]{${match.1}<a href=${match.2}${match.3} target="_new" title="Ссылка будет открыта в новом окне" class=smallest>${match.2}${match.3}</a>}]
				^if(^desc.length[] >= 250){
					$desc[^desc.left(250)&#133^; [<a href="$link">Читать дальше&#133^;</a>]]
				}
			}

			$cleanup[^table::create{from	to
<br>	<br />
<br /><br /><br /><br />	<br />
<br /><br /><br />	<br />
<br /><br />	<br />
blockquote><br /><br /><br />	blockquote>
blockquote><br /><br />	blockquote>
blockquote><br />	blockquote>
<br /></blockquote>	</blockquote>
</blockquote><br />	</blockquote>
</blockquote><br />	</blockquote>
</blockquote><br />	</blockquote>
</blockquote><br />	</blockquote>
</blockquote><br />	</blockquote>
</blockquote><br />	</blockquote>}]
	
			^if(def $desc){
	
				<blockquote>
					^desc.replace[$cleanup]
				</blockquote>

			}{<blockquote><i>Эта запись - без текста</i></blockquote>}
		}
}
На странице с френдлентой вызывается так:
	
	^rss:display[]
	
	$skip(^form:skip.int[])
	$skip(^eval($skip + 20))
	
	<p>[<a href="./?skip=${skip}">Следующие 20 записей&#133^;</a>]</p>
Рефреш вызывается так:
^rss:update_list[]
В коде упоминается класс dates, он неправильный, буду переписывать. Вот его код:
##############################
# Класс для работы с датами
@CLASS
dates

##############################
# Получаем текущую дату по Гринвичу из нашей локальной:
@load[]

# Нам нужно узнать, на сколько сдвинуть текущую дату и время.
# Сдвиг складывается из разницы по отношению к Гринвичу + поправки на летнее время

$local[^date::now[]]

# Летнее время:

^if($MAIN:global_daylight eq "yes"){

# Попадает ли текущая дата в промежуток, когда вводится летнее время?
	$daylight_lbound[^date::create($local.year;$MAIN:global_daylight_m_start;$MAIN:global_daylight_d_start;0;0;0)]
	$daylight_ubound[^date::create($local.year;$MAIN:global_daylight_m_end;$MAIN:global_daylight_d_end;0;0;0)]

	^if(
		($local.day >= $daylight_lbound.day) 
	&&
		($local.month >= $daylight_lbound.month) 
	&& 
		($local.day <= $daylight_ubound.day) 
	&&
		($local.month <= $daylight_ubound.month) 
	){
#		Текущая дата - в том, диапазоне, в котором летнее время в силе:
		$daylight_offset(1)
	}{
#		...или нет:
		$daylight_offset(0)
	}
}

# Из текущей даты и времени мы должны вычесть час летнего времени и разницу с Гринвичем:
$GMTdate[^date::create($local - (($daylight_offset + $MAIN:global_timeoffset)/24))]

##############################
# Получаем текущую дату (системную):
@local_datetime[]
$result[^local.sql-string[]]


##############################
# Получаем текущую дату (приведённую к Гринвичу):
@GMT_datetime[]
$result[^GMTdate.sql-string[]]

##############################
# Приводим дату в виде строки ("Mon, 29 Sep 2003 11:23:12 GMT") к Гринвичу:
@string2GMT[string]

^string.match[([A-Za-z]{3}), ([0-9]+) ([A-Za-z]{3}) ([0-9]+) ([0-9]+):([0-9]+):([0-9]+) ([A-Za-z]{3,4})][g]{
	$year($match.4)
	$month[$match.3]

	^if($month eq Jan){$month(1)}
	^if($month eq Feb){$month(2)}
	^if($month eq Mar){$month(3)}
	^if($month eq Apr){$month(4)}
	^if($month eq May){$month(5)}
	^if($month eq Jun){$month(6)}
	^if($month eq Jul){$month(7)}
	^if($month eq Aug){$month(8)}
	^if($month eq Sep){$month(9)}
	^if($month eq Oct){$month(10)}
	^if($month eq Nov){$month(11)}
	^if($month eq Dec){$month(12)}

	$day($match.2)
	$hour($match.5)
	$minute($match.6)
	$second($match.7)
	$timezonename[$match.8]
}

#	Табличка с названиями временных зон и поправками:
#	Название 	Аббр.	Поправка
#	Samoa Standard Time	SST	-11
#	Hawaii-Aleutian Standard Time  	HST	-10
#	Alaska Standard Time	AKST	-9
#	Hawaii-Aleutian Daylight Time  	HDT	-9
#	Alaska Daylight Time	AKDT	-8
#	Pacific Standard Time	PST	-8
#	Mountain Standard Time	MST	-7
#	Pacific Daylight Time	PDT	-7
#	Central Standard Time	CST	-6
#	Mountain Daylight Time	MDT	-6
#	Central Daylight Time	CDT	-5
#	Eastern Standard Time	EST	-5
#	Atlantic Standard Time	AST	-4
#	Eastern Daylight Time	EDT	-4
#	Atlantic Daylight Time	ADT	-3
#	Greenwich Mean Time	GMT	0
#	Western Europe Time	WET	0
#	British Summer Time	BST	1
#	Central Europe Time	CET	1
#	Irish Summer Time	IST	1
#	Western Europe Summer Time	WEST	1
#	Central Europe Summer Time	CEST	2
#	Eastern Europe Time	EET	2
#	Eastern Europe Summer Time	EEST	3
#	Moscow Time	MSK	3
#	Moscow Summer Time	MSD	4
#	Western Standard Time	WST	8
#	Central Standard Time	CST	9.5
#	Eastern Standard Time	EST	10

^if(def $year && def $month && def $day){
	^switch[$timezonename]{
		^case[SST]{$tz_offset(-11)}
		^case[HST]{$tz_offset(-10)}
		^case[AKST]{$tz_offset(-9)}
		^case[HDT]{$tz_offset(-9)}
		^case[AKDT]{$tz_offset(-8)}
		^case[PST]{$tz_offset(-8)}
		^case[MST]{$tz_offset(-7)}
		^case[PDT]{$tz_offset(-7)}
		^case[CST]{$tz_offset(-6)}
		^case[MDT]{$tz_offset(-6)}
		^case[CDT]{$tz_offset(-5)}
		^case[EST]{$tz_offset(-5)}
		^case[AST]{$tz_offset(-4)}
		^case[EDT]{$tz_offset(-4)}
		^case[ADT]{$tz_offset(-3)}
		^case[GMT]{$tz_offset(0)}
		^case[WET]{$tz_offset(0)}
		^case[BST]{$tz_offset(1)}
		^case[CET]{$tz_offset(1)}
		^case[IST]{$tz_offset(1)}
		^case[WEST]{$tz_offset(1)}
		^case[CEST]{$tz_offset(2)}
		^case[EET]{$tz_offset(2)}
		^case[EEST]{$tz_offset(3)}
		^case[MSK]{$tz_offset(3)}
		^case[MSD]{$tz_offset(4)}
		^case[WST]{$tz_offset(8)}
		^case[CST]{$tz_offset(9.5)}
		^case[EST]{$tz_offset(10)}
		^case[DEFAULT]{$tz_offset(0)}
	}
	$UnadjustedString[^date::create($year;$month;$day;$hour;$minute;$second)]
	$GMTString[^date::create($UnadjustedString - ($tz_offset/24))]
}{
	$GMTString[^date::now[]]
}

$result[^GMTString.sql-string[]]
В auto.p вынесены следующие переменные:
$global_timeoffset(3)
$global_timeoffset_string[+03:00]

# Ваше локальное время сдвигается летом?
$global_daylight[yes]

# Это происходит с [день, месяц] по [день, месяц]:
# (обычно с 29-Mar по 25-Oct, см. http://www.worldtimezone.com/daylight.htm)

$global_daylight_m_start(3)
$global_daylight_d_start(29)

$global_daylight_m_end(10)
$global_daylight_d_end(25)