# -*-Ruby-*-
require 'rubygems'
require 'rexml/document'
include REXML
require 'fox16'
include Fox
require 'time'
# REXML and FXRuby 1.6 internal encoding is UTF-8
# ours is ISO-8859-1
class Formatter
Tab = " "
def Formatter.utf8ToLatin1(text)
str = String.new
converter = Output.new(str, "iso-8859-1")
converter << text
return str
end
end
class Item
attr_accessor(:title, :link, :description, :pubDate)
def initialize
@title = ''
@link = ''
@description = ''
@pubDate = Time.now.rfc822
end
def computeDescription
return @description
end
def computeHtmlDescription
return @description.gsub(/\[([^\]]*)\]/, '\1')
end
def getLocaltime
Time.parse(@pubDate).localtime
end
def getAnchorLink
getLink + '#' + getAnchor
end
def getAnchor
getLocaltime.strftime('%d%m%Y%H%M')
end
def getLink
@link.gsub("\\", "/").gsub(/#.*/, "")
end
def xml(indent='')
subIndent = indent + Formatter::Tab
str = "#{indent}- \n"
str << "#{subIndent}#{@title}\n"
str << "#{subIndent}#{getAnchorLink}\n"
str << "#{subIndent}#{computeDescription}\n"
str << "#{subIndent}#{@pubDate}\n" unless @pubDate.empty?
str << "#{indent}
\n"
return str
end
def html(indent='')
subIndent = indent + Formatter::Tab
time = getLocaltime
str = "#{indent}\n"
str << "#{indent}
\n"
str << "#{subIndent}
#{@title}
\n"
str << "#{subIndent}
#{computeHtmlDescription}
\n"
str << "#{subIndent}
#{time.strftime('%d.%m.%Y, %H:%M')}
\n" unless @pubDate.empty?
str << "#{indent}
\n"
end
end
class Channel
attr_accessor(:title, :link, :description, :items)
def initialize(maxNumOfItems=10)
@title = ''
@link = ''
@description = ''
@items = Array.new
@maxNumOfItems = maxNumOfItems
end
def appendOldItem(item)
@items.push(item) if @items.size < @maxNumOfItems
end
def addNewItem(item)
@items.reverse!
@items.shift if @items.size == @maxNumOfItems
@items.push(item)
@items.reverse!
end
def xml(indent='')
subIndent = indent + Formatter::Tab
str = String.new
str << "#{indent}\n"
str << "#{subIndent}#{@title}\n"
str << "#{subIndent}#{@link.gsub("\\", "/")}\n"
str << "#{subIndent}#{@description}\n"
@items.each { |i| str << i.xml(subIndent) }
str << "#{indent}\n"
return str
end
def html(indent='')
subIndent = indent + Formatter::Tab
str = String.new
@items.each { |i| str << i.html(subIndent) }
return str
end
end
class RssFeed
attr_reader(:channel)
def initialize
@channel = Channel.new
end
def addItem(item)
@channel.addNewItem(item)
end
def readFile(file)
doc = Document.new(File.new(file))
doc.ignore_whitespace_nodes
@channel = Channel.new
numChan = 0
channels = doc.elements.each("rss/channel") do
|channelsXml|
Exeption.new("More than one Channel in #{file}") if (numChan += 1) > 1
channelsXml.elements.each do
|elemXml|
case elemXml.name
when 'title' then @channel.title = getString(elemXml)
when 'link' then @channel.link = getString(elemXml)
when 'description' then @channel.description = getString(elemXml)
when 'item' then computeItem(elemXml)
end
end
end
end
def writeFile(file, includeFormat=false)
File.open(file, 'w') do
| fp |
fp.print("\n")
if includeFormat
fp.print("\n")
fp.print("\n")
end
fp.print("\n")
fp.print(Formatter.utf8ToLatin1(@channel.xml))
fp.print("\n")
end
end
def replaceInHtmlFile(file)
lines = File.readlines(file)
replacing = false
File.open(file, 'w') { |fp|
lines.each { |line|
replacing = false if line =~ //
fp.print(line) unless replacing
if line =~ //
replacing = true
fp.print(Formatter.utf8ToLatin1(@channel.html))
end
}
}
end
private
def computeItem(xmlItem)
item = Item.new
item.pubDate = ''
xmlItem.elements.each do
|element|
case element.name
when 'title' then item.title = getString(element)
when 'link' then item.link = getString(element)
when 'description' then item.description = getString(element)
when 'pubDate' then item.pubDate = getString(element)
end
end
@channel.appendOldItem(item)
end
def getString(element)
element.text.nil? ? "" : element.text
end
end
class RssWriterGui < Fox::FXMainWindow
include Fox
include Responder
MsgStyle = MBOX_OK|DECOR_TITLE|DECOR_CLOSE|PLACEMENT_DEFAULT
def initialize(app)
super(app, "Very Simple Rss Feed Editor", nil, nil, DECOR_ALL,
0, 0, 160, 160, 0, 0)
@formattedFeedFile = 'feed.xml'
@feedFile = 'valid-feed.xml'
@htmlFile = 'news.htm'
@feed = RssFeed.new
@selectedItem = nil
@titles = Array.new
initView
end
def initFrame(aFrame, fillMode=LAYOUT_FILL_X|LAYOUT_FILL_Y)
aFrame.setFrameStyle(FRAME_NONE)
aFrame.setLayoutHints(fillMode)
return aFrame
end
def initView
basicFrame = initFrame(FXVerticalFrame.new(self))
gridFrame = FXMatrix.new(basicFrame, 3, MATRIX_BY_COLUMNS)
gridFrame.setLayoutHints(LAYOUT_FILL_X)
label = FXLabel.new(gridFrame, "Channel-Link:")
label.setLayoutHints(LAYOUT_CENTER_Y)
@linkTextField = FXTextField.new(gridFrame, 30)
@linkTextField.setLayoutHints(LAYOUT_CENTER_Y|LAYOUT_FILL_X|LAYOUT_FILL_COLUMN)
emptyFrame = initFrame(FXVerticalFrame.new(gridFrame))
emptyFrame.setLayoutHints(LAYOUT_CENTER_Y|LAYOUT_FILL_X|LAYOUT_FILL_COLUMN)
label = FXLabel.new(gridFrame, "Item-Title:")
label.setLayoutHints(LAYOUT_CENTER_Y)
@itemsCombobox = FXComboBox.new(gridFrame, 24,
nil, 0, COMBOBOX_REPLACE)
@itemsCombobox.setFrameStyle(FRAME_SUNKEN|FRAME_THICK)
@itemsCombobox.setLayoutHints(LAYOUT_CENTER_Y|LAYOUT_FILL_X|LAYOUT_FILL_COLUMN)
addItemButton = FXButton.new(gridFrame, "New Item")
addItemButton.connect(SEL_COMMAND, method(:onCmdAddItem))
addItemButton.setLayoutHints(LAYOUT_CENTER_Y)
FXLabel.new(basicFrame, "Description:")
@descriptionTextField = FXText.new(basicFrame)
@descriptionTextField.setLayoutHints(LAYOUT_FILL_X|LAYOUT_FILL_Y)
@descriptionTextField.connect(SEL_CHANGED, method(:onCmdDescriptionChanged))
buttonFrame = initFrame(FXHorizontalFrame.new(basicFrame), LAYOUT_FILL_X)
generateButton = FXButton.new(buttonFrame, "Save Feed")
generateButton.setButtonStyle(BUTTON_DEFAULT)
generateButton.setDefault
generateButton.connect(SEL_COMMAND, method(:onCmdGenerate))
setDefaults
end
def setDefaults
urlString = $*.size > 0 ? $*[0] : "http://"
@linkTextField.setText(urlString)
@descriptionTextField.setText("")
end
# Create and show window
def create
position(0, 0, 340, 300)
super
show(PLACEMENT_SCREEN)
readFeed
end
def readFeed
begin
@feed = RssFeed.new
@feed.readFile(@feedFile)
initCombobox
@linkTextField.setText(@feed.channel.link)
rescue Exception => err
msg = "Failed with error: #{err.message}\n"
msg << "Quit, otherwise I'll start with an empty feed"
@descriptionTextField.setText "Failed with error: #{err.message}"
@feed = RssFeed.new
end
end
def initCombobox
newComboBoxList
numVisible = @titles.size >= 10 && @titles.size <= 20 ? @titles.size : 10
@itemsCombobox.setNumVisible(numVisible)
@itemsCombobox.connect(SEL_COMMAND, method(:onCmdSelectItem))
@itemsCombobox.connect(SEL_CHANGED, method(:onCmdTitleChanged))
selectItem(@feed.channel.items.first)
@itemsCombobox.setCurrentItem(0)
end
def newComboBoxList
@itemsCombobox.clearItems
@titles = @feed.channel.items.collect{ |item| item.title }
@titles.each { |title| @itemsCombobox.appendItem(title) }
end
def selectItem(item)
@descriptionTextField.setText(item.description)
@selectedItem = item
end
def copyViewToSelectedItem
return if(@selectedItem.nil?)
@selectedItem.title = @itemsCombobox.getText
@selectedItem.link = @linkTextField.getText
@selectedItem.description = @descriptionTextField.getText
end
def overwriteFeed
begin
@feed.writeFile(@feedFile, false)
@feed.writeFile(@formattedFeedFile, true)
@feed.replaceInHtmlFile(@htmlFile)
rescue Exception => err
msg = "Failed with error: #{err.message}\n"
msg << "Unable to recover - files might be damaged"
@descriptionTextField.setText "Failed with error: #{err.message}"
end
end
def onCmdQuit(sender, sel, ptr)
getApp.exit(0)
end
def onCmdTitleChanged(sender, sel, data)
copyViewToSelectedItem
end
def onCmdDescriptionChanged(sender, sel, data)
copyViewToSelectedItem
end
def onCmdSelectItem(sender, sel, selectedTitle)
index = @itemsCombobox.getCurrentItem
selectItem(@feed.channel.items[index]) unless(index.nil?)
end
def onCmdAddItem(sender, sel, index)
copyViewToSelectedItem
item = Item.new
item.title = "New Item"
item.link = @linkTextField.getText
item.description = ""
@feed.addItem(item)
newComboBoxList
@itemsCombobox.setCurrentItem(0)
selectItem(item)
end
def onCmdGenerate(sender, sel, index)
getApp.beginWaitCursor
overwriteFeed
getApp.endWaitCursor
end
end
def runRssWriterGui
application = Fox::FXApp.new("RssWriterGui", "Sascha Dördelmann")
window = RssWriterGui.new(application)
application.addSignal("SIGINT", window.method(:onCmdQuit))
application.create
application.run
end
runRssWriterGui