# -*-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