2 Stylesheet to convert Twitter user timelines to RSS.
4 Copyright (C) 2013-2020 Antonio Ospite <ao2@ao2.it>
6 This file is part of tweeper.
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 <xsl:stylesheet version="1.0"
22 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
23 xmlns:php="http://php.net/xsl"
24 exclude-result-prefixes="php">
26 <xsl:param name="generate-enclosure"/>
27 <xsl:param name="show-usernames"/>
28 <xsl:param name="show-multimedia"/>
30 <xsl:output method="xml" indent="yes"/>
32 <xsl:variable name="BaseURL">
33 <xsl:text>https://twitter.com</xsl:text>
36 <!-- Identity transform -->
37 <xsl:template match="@*|node()">
40 Strip the style attribute while copying elements because it may be
42 https://validator.w3.org/feed/docs/warning/DangerousStyleAttr.html
44 <xsl:apply-templates select="@*[not(name() = 'style')]|node()"/>
48 <!-- Strip leading spaces in first text node of the tweet-text. -->
49 <xsl:template match="div[@class='tweet-text']/div/text()[1]">
50 <xsl:value-of select="substring-after(substring-after(., ' '), ' ')"/>
54 Anchors to external links provide the direct URL in the
55 data-expanded-url attribute, so use this in the href attribute too
56 instead of the default short URL which uses the t.co redirection
59 NOTE: when creating an element, attributes must be processed _before_
60 adding the contents (either children or a value):
61 http://stackoverflow.com/questions/21984867/
63 <xsl:template match="a[@data-expanded-url]">
65 <xsl:attribute name="href">
66 <xsl:value-of select="@data-expanded-url"/>
68 <xsl:value-of select="@data-expanded-url"/>
73 These are links to pic.twitter.com, use the direct link for those
74 too instead of the t.co redirections.
76 <xsl:template match="a[@data-pre-embedded='true']">
77 <xsl:if test="$show-multimedia = 1">
79 <xsl:attribute name="href">
80 <xsl:value-of select="@data-url"/>
82 <xsl:value-of select="concat('https://', .)"/>
87 <!-- Present images in a more convenient way -->
88 <!-- TODO: not supported in mobile UI
89 <xsl:template match="a[@data-pre-embedded='true' and contains(@data-url, '/photo/')]">
90 <xsl:variable name="embedded-photo-url" select="concat('https://pbs.twimg.com/media/', @data-tco-id, '?format=jpg')"/>
92 <xsl:attribute name="href">
93 <xsl:value-of select="$embedded-photo-url"/>
95 <img style="max-width: 100%">
96 <xsl:attribute name="src">
97 <xsl:value-of select="$embedded-photo-url"/>
104 <!-- Don't repeat background in embedded media content -->
105 <!-- TODO: not supported in mobile UI
106 <xsl:template match="div[contains(@class, 'PlayableMedia-player')]">
108 <xsl:apply-templates select="@*"/>
109 <xsl:attribute name="style">
110 <xsl:value-of select="concat(@style, '; background-repeat: no-repeat; background-size: 100% auto')"/>
112 <xsl:apply-templates select="node()"/>
117 <xsl:template match="a[@data-expanded-url]" mode="enclosure">
118 <xsl:copy-of select="php:functionString('Tweeper\Tweeper::generateEnclosure', ./@data-expanded-url)"/>
121 <xsl:template match="a[@data-pre-embedded='true']" mode="enclosure">
122 <xsl:copy-of select="php:functionString('Tweeper\Tweeper::generateEnclosure', @data-url)"/>
125 <xsl:variable name="screen-name" select="normalize-space(substring-after(//table[@class='profile-details' or @class='main-tweet']//*[@class='username'], '@'))"/>
127 <xsl:template match="//div[contains(@class, 'timeline')]/table[@class='tweet ']|//div[@class='main-tweet-container']/table[@class='main-tweet']">
128 <xsl:variable name="user-name" select="normalize-space(.//*[@class='username']/text()[2])"/>
129 <xsl:variable name="item-content" select=".//div[@class='tweet-text']/div"/>
130 <xsl:variable name="item-media" select=".//a[@data-pre-embedded='true']"/>
131 <xsl:variable name="item-permalink">
133 <xsl:when test="@href">
134 <xsl:value-of select="concat($BaseURL, substring-before(@href, '?'))"/>
138 The main tweet in permalink pages do not have a timestamp tag,
139 just use the canonical URL as permalink.
141 <xsl:value-of select="//link[@rel='canonical']/@href"/>
146 <!-- TODO twitter mobile UI does not have a way to detect this
147 <xsl:variable name="item-has-video" select="$item-media//*[contains(@class, 'PlayableMedia- -video')]"/>
148 <xsl:variable name="item-has-gif" select="$item-media//*[contains(@class, 'PlayableMedia- -gif')]"/>
153 <xsl:if test="($show-usernames = 1) or ($screen-name != $user-name)">
154 <xsl:value-of select="concat($user-name, ': ')"/>
156 <!-- TODO twitter mobile UI does not have a way to detect this
157 <xsl:if test="$item-has-video">
158 <xsl:text>(Video) </xsl:text>
162 Prepend a space in front of the URLs which are not
163 preceded by an open parenthesis, for aestethic reasons.
164 Also, regex, I know: http://xkcd.com/1171/
167 name="processed-title"
168 select="php:functionString('preg_replace', '@((?<!\()(?:http[s]?://|pic.twitter.com))@', ' \1', $item-content)"/>
169 <!-- Also strip and … -->
170 <xsl:value-of select="normalize-space(translate($processed-title, ' …', ''))"/>
173 <xsl:value-of select="$item-permalink"/>
176 <xsl:value-of select="$item-permalink"/>
179 <xsl:variable name="timestamp" select=".//td[@class='timestamp']/a|.//div[@class='metadata']/a"/>
180 <xsl:value-of select="php:functionString('Tweeper\Tweeper::twitterToRssDate', $timestamp)"/>
183 <xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
184 <xsl:if test="($show-usernames = 1) or ($screen-name != $user-name)">
185 <xsl:value-of select="concat($user-name, ':')"/>
186 <xsl:element name="br"/>
188 <!-- TODO twitter mobile UI does not support embedded media
189 <xsl:if test="$item-has-video">
190 <xsl:text> (Video)</xsl:text>
191 <xsl:element name="br"/>
193 <xsl:if test="$item-has-gif">
194 <xsl:text> (GIF)</xsl:text>
195 <xsl:element name="br"/>
198 <xsl:element name="span">
199 <xsl:attribute name="style">white-space: pre-wrap;</xsl:attribute>
200 <xsl:apply-templates select="$item-content/node()"/>
203 <!-- TODO twitter mobile UI does not support embedded media
204 <xsl:if test="$show-multimedia = 1">
205 <xsl:apply-templates select="$item-media"/>
208 <xsl:text disable-output-escaping="yes">]]></xsl:text>
210 <xsl:if test="$generate-enclosure = 1">
211 <xsl:apply-templates select="$item-content//a[@data-expanded-url]" mode="enclosure"/>
212 <xsl:apply-templates select="$item-media" mode="enclosure"/>
217 <xsl:template match="/">
218 <xsl:variable name="channel-title">
220 <xsl:when test="$screen-name != ''">
221 <xsl:value-of select="concat('Twitter / ', $screen-name)"/>
224 <xsl:value-of select="concat('Twitter / ', normalize-space(//td[@id='search']//input/@value))"/>
228 <xsl:variable name="channel-link" select="//link[@rel='canonical']/@href"/>
229 <xsl:variable name="channel-image" select="//table[@class='profile-details' or @class='main-tweet']//td[@class='avatar']//img/@src"/>
232 <xsl:attribute name="xml:base"><xsl:value-of select="$BaseURL" /></xsl:attribute>
234 <generator>Tweeper</generator>
236 <xsl:value-of select="$channel-title"/>
239 <xsl:value-of select="$channel-link"/>
242 <xsl:value-of select="normalize-space(//table[@class='profile-details' or @class='main-tweet']//td[@class='details'])"/>
244 <xsl:if test="$channel-image != ''">
247 <xsl:value-of select="$channel-title"/>
250 <xsl:value-of select="$channel-link"/>
253 <xsl:value-of select="$channel-image"/>
257 <xsl:apply-templates select="//div[contains(@class, 'timeline')]/table[@class='tweet ']|//div[@class='main-tweet-container']/table[@class='main-tweet']"/>