Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- do
- Object = { __name = "Object" }
- function Object:init(...) end
- function Object:super(class)
- local obj = self
- return setmetatable({ }, { __index = function(t, k)
- return function(...)
- while class do
- local method = class[k]
- if method then
- return method(obj, ...)
- end
- class = class.__super
- end
- end
- end })
- end
- function Object:createTable()
- local obj = self
- local class = obj:getClass()
- return setmetatable({ }, { __index = function(t, k)
- return function(...)
- local method = class[k]
- return method(obj, ...)
- end
- end })
- end
- function Object:getSuper()
- return rawget(self:getClass(), "__super")
- end
- function Object:getClass()
- return rawget(self, "__class")
- end
- function Object:instanceof(targetType)
- local objectType = self:getClass()
- while objectType do
- if targetType == objectType then
- return true
- end
- objectType = objectType:getSuper()
- end
- return false
- end
- function Object:toString()
- return self:getClass():getName().." ("..tostring(self):sub(8, -1)..")"
- end
- function Object:extend(obj)
- self.__index = self -- This is a metatable
- local obj = obj or {}
- obj.__super = self
- local obj = setmetatable(obj, self)
- return obj
- end
- Class = Object:extend({__name = "Class"})
- function Class:new(...)
- local instance = { __class = self }
- self:extend(instance)
- instance:init(...)
- return instance
- end
- function Class:getName(name)
- return self.__name
- end
- function Class:subClass(name)
- local instance = { }
- instance.__name = name
- return self:extend(instance)
- end
- end
- do
- local log = fs.open("BobCat.log", "w")
- function Debug(Message, ...)
- Log("DEBUG", Message, ...)
- end
- function Print(Message, ...)
- print(Log("PRINT", Message, ...))
- end
- function PrintError(Message, ...)
- printError(Log("WARNING", Message, ...))
- end
- function Error(Message, ...)
- error(Log("ERROR", Message, ...))
- end
- function Log(Type, Message, ...)
- local str = string.format(tostring(Message), unpack({...}))
- log.writeLine("["..Type.."] "..str)
- log.flush()
- return str
- end
- end
- do
- --[=====================================================================[
- v0.6 Copyright © 2013-2014 Gavin Kistner <[email protected]>; MIT Licensed
- See http://github.com/Phrogz/SLAXML for details.
- --]=====================================================================]
- SLAXML = {
- VERSION = "0.6",
- _call = {
- pi = function(target,content)
- print(string.format("<?%s %s?>",target,content))
- end,
- comment = function(content)
- print(string.format("<!-- %s -->",content))
- end,
- startElement = function(name,nsURI,nsPrefix)
- io.write("<")
- if nsPrefix then io.write(nsPrefix,":") end
- io.write(name)
- if nsURI then io.write(" (ns='",nsURI,"')") end
- print(">")
- end,
- attribute = function(name,value,nsURI,nsPrefix)
- io.write(' ')
- if nsPrefix then io.write(nsPrefix,":") end
- io.write(name,'=',string.format('%q',value))
- if nsURI then io.write(" (ns='",nsURI,"')") end
- io.write("\n")
- end,
- text = function(text)
- print(string.format(" text: %q",text))
- end,
- closeElement = function(name,nsURI,nsPrefix)
- print(string.format("</%s>",name))
- end,
- }
- }
- function SLAXML:parser(callbacks)
- return { _call=callbacks or self._call, parse=SLAXML.parse }
- end
- function SLAXML:parse(xml,options)
- if not options then options = { stripWhitespace=false } end
- -- Cache references for maximum speed
- local find, sub, gsub, char, push, pop = string.find, string.sub, string.gsub, string.char, table.insert, table.remove
- local first, last, match1, match2, match3, pos2, nsURI
- local unpack = unpack or table.unpack
- local pos = 1
- local state = "text"
- local textStart = 1
- local currentElement={}
- local currentAttributes={}
- local currentAttributeCt -- manually track length since the table is re-used
- local nsStack = {}
- local entityMap = { ["lt"]="<", ["gt"]=">", ["amp"]="&", ["quot"]='"', ["apos"]="'" }
- local entitySwap = function(orig,n,s) return entityMap[s] or n=="#" and char(s) or orig end
- local function unescape(str) return gsub( str, '(&(#?)([%d%a]+);)', entitySwap ) end
- local anyElement = false
- local function finishText()
- if first>textStart and self._call.text then
- local text = sub(xml,textStart,first-1)
- if options.stripWhitespace then
- text = gsub(text,'^%s+','')
- text = gsub(text,'%s+$','')
- if #text==0 then text=nil end
- end
- if text then self._call.text(unescape(text)) end
- end
- end
- local function findPI()
- first, last, match1, match2 = find( xml, '^<%?([:%a_][:%w_.-]*) ?(.-)%?>', pos )
- if first then
- finishText()
- if self._call.pi then self._call.pi(match1,match2) end
- pos = last+1
- textStart = pos
- return true
- end
- end
- local function findComment()
- first, last, match1 = find( xml, '^<!%-%-(.-)%-%->', pos )
- if first then
- finishText()
- if self._call.comment then self._call.comment(match1) end
- pos = last+1
- textStart = pos
- return true
- end
- end
- local function nsForPrefix(prefix)
- if prefix=='xml' then return 'http://www.w3.org/XML/1998/namespace' end -- http://www.w3.org/TR/xml-names/#ns-decl
- for i=#nsStack,1,-1 do if nsStack[i][prefix] then return nsStack[i][prefix] end end
- error(("Cannot find namespace for prefix %s"):format(prefix))
- end
- local function startElement()
- anyElement = true
- first, last, match1 = find( xml, '^<([%a_][%w_.-]*)', pos )
- if first then
- currentElement[2] = nil -- reset the nsURI, since this table is re-used
- currentElement[3] = nil -- reset the nsPrefix, since this table is re-used
- finishText()
- pos = last+1
- first,last,match2 = find(xml, '^:([%a_][%w_.-]*)', pos )
- if first then
- currentElement[1] = match2
- currentElement[3] = match1 -- Save the prefix for later resolution
- match1 = match2
- pos = last+1
- else
- currentElement[1] = match1
- for i=#nsStack,1,-1 do if nsStack[i]['!'] then currentElement[2] = nsStack[i]['!']; break end end
- end
- currentAttributeCt = 0
- push(nsStack,{})
- return true
- end
- end
- local function findAttribute()
- first, last, match1 = find( xml, '^%s+([:%a_][:%w_.-]*)%s*=%s*', pos )
- if first then
- pos2 = last+1
- first, last, match2 = find( xml, '^"([^<"]*)"', pos2 ) -- FIXME: disallow non-entity ampersands
- if first then
- pos = last+1
- match2 = unescape(match2)
- else
- first, last, match2 = find( xml, "^'([^<']*)'", pos2 ) -- FIXME: disallow non-entity ampersands
- if first then
- pos = last+1
- match2 = unescape(match2)
- end
- end
- end
- if match1 and match2 then
- local currentAttribute = {match1,match2}
- local prefix,name = string.match(match1,'^([^:]+):([^:]+)$')
- if prefix then
- if prefix=='xmlns' then
- nsStack[#nsStack][name] = match2
- else
- currentAttribute[1] = name
- currentAttribute[4] = prefix
- end
- else
- if match1=='xmlns' then
- nsStack[#nsStack]['!'] = match2
- currentElement[2] = match2
- end
- end
- currentAttributeCt = currentAttributeCt + 1
- currentAttributes[currentAttributeCt] = currentAttribute
- return true
- end
- end
- local function findCDATA()
- first, last, match1 = find( xml, '^<!%[CDATA%[(.-)%]%]>', pos )
- if first then
- finishText()
- if self._call.text then self._call.text(match1) end
- pos = last+1
- textStart = pos
- return true
- end
- end
- local function closeElement()
- first, last, match1 = find( xml, '^%s*(/?)>', pos )
- if first then
- state = "text"
- pos = last+1
- textStart = pos
- -- Resolve namespace prefixes AFTER all new/redefined prefixes have been parsed
- if currentElement[3] then currentElement[2] = nsForPrefix(currentElement[3]) end
- if self._call.startElement then self._call.startElement(unpack(currentElement)) end
- if self._call.attribute then
- for i=1,currentAttributeCt do
- if currentAttributes[i][4] then currentAttributes[i][3] = nsForPrefix(currentAttributes[i][4]) end
- self._call.attribute(unpack(currentAttributes[i]))
- end
- end
- if match1=="/" then
- pop(nsStack)
- if self._call.closeElement then self._call.closeElement(unpack(currentElement)) end
- end
- return true
- end
- end
- local function findElementClose()
- first, last, match1, match2 = find( xml, '^</([%a_][%w_.-]*)%s*>', pos )
- if first then
- nsURI = nil
- for i=#nsStack,1,-1 do if nsStack[i]['!'] then nsURI = nsStack[i]['!']; break end end
- else
- first, last, match2, match1 = find( xml, '^</([%a_][%w_.-]*):([%a_][%w_.-]*)%s*>', pos )
- if first then nsURI = nsForPrefix(match2) end
- end
- if first then
- finishText()
- if self._call.closeElement then self._call.closeElement(match1,nsURI) end
- pos = last+1
- textStart = pos
- pop(nsStack)
- return true
- end
- end
- while pos<#xml do
- if state=="text" then
- if not (findPI() or findComment() or findCDATA() or findElementClose()) then
- if startElement() then
- state = "attributes"
- else
- first, last = find( xml, '^[^<]+', pos )
- pos = (first and last or pos) + 1
- end
- end
- elseif state=="attributes" then
- if not findAttribute() then
- if not closeElement() then
- print(state)
- error("Was in an element and couldn't find attributes or the close.")
- end
- end
- end
- end
- if not anyElement then error("Parsing did not discover any elements") end
- if #nsStack > 0 then error("Parsing ended with unclosed elements") end
- end
- end
- do
- --@require Class.lua
- --Base class for pages
- Page = Class:subClass("Page")
- function Page:init(Url)
- self.Url = Url
- self.Title = Url
- self.Ids = { }
- self.Clicks = { }
- end
- end
- do
- --@require ../../Class.lua
- --Base class for elements inside the DOM tree
- Element = Class:subClass("Element")
- function Element:init(Data, Page)
- self.Type = Data.Type
- self.Tag = Data.Tag
- self.Page = Page
- self.Parent = nil
- end
- function Element:setParent(Parent)
- self.Parent = Parent
- end
- function Element:draw(Gui) end
- function Element:stripWhitespace() end
- function Element:previousElement()
- local previous = nil
- for _, Child in pairs(self.Parent.Children) do
- if Child == self then
- break
- end
- previous = Child
- end
- return previous
- end
- function Element:nextElement()
- local nextElement = nil
- for _, Child in pairs(self.Parent.Children) do
- if nextElement then
- break
- end
- if Child == self then
- nextElement = true
- end
- end
- return nextElement
- end
- --Text elements
- TextElement = Element:subClass("TextElement")
- function TextElement:init(Data, Page)
- self:super(Element).init(Data, Page)
- self.Contents = Data.Value or ""
- end
- function TextElement:toString()
- return self.Contents
- end
- function TextElement:draw(Buffer)
- --Buffer:print(self.Contents)
- write(self.Contents)
- end
- function TextElement:stripWhitespace()
- self.Contents = self.Contents:gsub("%s+", " ")
- end
- end
- do
- --@require ../Class.lua
- --@require ../Logger.lua
- local Tags = { }
- local DefaultClass = nil
- TagRegister = Class:subClass("TagRegister")
- function TagRegister.Register(tag, class)
- Debug("Registering %s for tag %s", class:getName(), tag)
- Tags[tag] = class
- end
- function TagRegister.SetDefaultClass(class)
- DefaultClass = class
- end
- function TagRegister.CreateElement(Data, ThisPage)
- local EClass = Tags[Data.Tag]
- if EClass == nil then
- EClass = DefaultClass
- end
- return EClass:new(Data, ThisPage)
- end
- end
- do
- --@require ../Class.lua
- --From Lyqyd's FrameBuffer API (http://pastebin.com/Aaza6h5v)
- FrameBuffer = Class:subClass("FrameBuffer")
- function FrameBuffer:init(sizeX, sizeY, colour)
- self.text = {}
- self.textColor = {}
- self.backColor = {}
- self.cursorX = 1
- self.cursorY = 1
- self.curTextColor = "0"
- self.curBackColor = "f"
- self.sizeX = sizeX or 51
- self.sizeY = sizeY or -1
- self.endY = sizeY or 0
- if colour == nil then colour = true end
- self.color = colour
- self:createLine()
- end
- function FrameBuffer:write(text)
- text = tostring(text)
- --Debug("Writing %s (%s, %s)", text, self.curTextColor, self.curBackColor)
- local pos = self.cursorX
- if (self.cursorY > self.sizeY and self.sizeY ~= -1) or self.cursorY < 1 then
- self.cursorX = pos + #text
- return
- end
- if pos + #text <= 1 then
- --skip entirely.
- self.cursorX = pos + #text
- return
- elseif pos < 1 then
- --adjust text to fit on screen starting at one.
- writeText = string.sub(text, math.abs(self.cursorX) + 2)
- self.cursorX = 1
- elseif pos > self.sizeX then
- --if we're off the edge to the right, skip entirely.
- self.cursorX = pos + #text
- return
- else
- writeText = text
- end
- local lineText = self.text[self.cursorY]
- local lineColor = self.textColor[self.cursorY]
- local lineBack = self.backColor[self.cursorY]
- local preStop = self.cursorX - 1
- local preStart = math.min(1, preStop)
- local postStart = self.cursorX + string.len(writeText)
- local postStop = self.sizeX
- self.text[self.cursorY] = string.sub(lineText, preStart, preStop)..writeText..string.sub(lineText, postStart, postStop)
- self.textColor[self.cursorY] = string.sub(lineColor, preStart, preStop)..string.rep(self.curTextColor, #writeText)..string.sub(lineColor, postStart, postStop)
- self.backColor[self.cursorY] = string.sub(lineBack, preStart, preStop)..string.rep(self.curBackColor, #writeText)..string.sub(lineBack, postStart, postStop)
- self.cursorX = pos + string.len(text)
- end
- function FrameBuffer:createLine(line)
- if line==nil then
- self.endY = self.endY + 1
- line = self.endY
- elseif line > self.endY then
- self.endY = line
- end
- --Debug("Creating line at: %s", line)
- self.text[line] = string.rep(" ", self.sizeX)
- self.textColor[line] = string.rep(self.curTextColor, self.sizeX)
- self.backColor[line] = string.rep(self.curBackColor, self.sizeX)
- end
- function FrameBuffer:clearLine()
- self:createLine(self.cursorY)
- end
- function FrameBuffer:clear()
- self.text = {}
- self.textColor = {}
- self.backColor = {}
- self.cursorX = 1
- self.cursorY = 1
- self.curTextColor = "0"
- self.curBackColor = "f"
- self.endY = self.sizeY or 0
- end
- function FrameBuffer:getCursorPos()
- return self.cursorX, self.cursorY
- end
- function FrameBuffer:setCursorPos(x, y)
- self.cursorX = math.floor(tonumber(x)) or self.cursorX
- self.cursorY = math.floor(tonumber(y)) or self.cursorY
- --Debug("Cursor to %s, %s", x,y)
- if not self.text[y] then
- local last = nil
- for i = self.endY+1, y,1 do
- if last ~= i then
- self:createLine(i)
- last = i
- end
- end
- end
- end
- function FrameBuffer:setCursorBlink(b) end
- function FrameBuffer:scroll(n)
- self.cursorX = self.cursorX + n
- end
- function FrameBuffer:getSize()
- local y = self.sizeY
- if y == -1 then
- y = self.endY + 1000
- end
- return self.sizeX, y
- end
- function FrameBuffer:setTextColor(clr)
- if clr and clr <= 32768 and clr >= 1 then
- if self.color then
- self.curTextColor = string.format("%x", math.floor(math.log(clr) / math.log(2)))
- elseif clr == 1 or clr == 32768 then
- self.curTextColor = string.format("%x", math.floor(math.log(clr) / math.log(2)))
- else
- return nil, "Colour not supported"
- end
- end
- end
- FrameBuffer.setTextColour = FrameBuffer.setTextColor
- function FrameBuffer:setBackgroundColor(clr)
- if clr and clr <= 32768 and clr >= 1 then
- if self.color then
- self.curBackColor = string.format("%x", math.floor(math.log(clr) / math.log(2)))
- elseif clr == 32768 or clr == 1 then
- self.curBackColor = string.format("%x", math.floor(math.log(clr) / math.log(2)))
- else
- return nil, "Colour not supported"
- end
- end
- end
- FrameBuffer.setBackgroundColour = FrameBuffer.setBackgroundColor
- function FrameBuffer:isColor()
- return self.color == true
- end
- FrameBuffer.isColour = FrameBuffer.isColor
- function FrameBuffer:draw(redirect)
- local x, y = redirect.getCursorPos()
- for p=1, self.endY do
- self:drawLine(redirect, p)
- end
- redirect.setCursorPos(x + self.cursorX, y + self.cursorY)
- redirect.setTextColor(2 ^ tonumber(self.curTextColor, 16))
- redirect.setBackgroundColor(2 ^ tonumber(self.curBackColor, 16))
- end
- function FrameBuffer:drawLine(redirect, line)
- local x, y = redirect.getCursorPos()
- local w, h = redirect.getSize()
- local thisY = line + y
- --redirect.setCursorPos(x, thisY)
- --If off the edge
- if not self.text[line] then
- return
- end
- local lineEnd = false
- local offset = x
- while not lineEnd do
- local textColorString = string.match(string.sub(self.textColor[line], offset), string.sub(self.textColor[line], offset, offset).."*")
- local backColorString = string.match(string.sub(self.backColor[line], offset), string.sub(self.backColor[line], offset, offset).."*")
- redirect.setTextColor(2 ^ tonumber(string.sub(textColorString, 1, 1), 16))
- redirect.setBackgroundColor(2 ^ tonumber(string.sub(backColorString, 1, 1), 16))
- redirect.write(string.sub(self.text[line], offset, offset + math.min(#textColorString, #backColorString) - 1))
- offset = offset + math.min(#textColorString, #backColorString)
- if offset > self.sizeX then lineEnd = true end
- end
- end
- function FrameBuffer:getColours()
- return {2 ^ tonumber(string.sub(self.curTextColor, 1, 1), 16), 2 ^ tonumber(string.sub(self.curBackColor, 1, 1), 16)}
- end
- function FrameBuffer:restoreColours(colours)
- self:setTextColour(colours[1])
- self:setBackgroundColour(colours[2])
- end
- function FrameBuffer:resetColours()
- self:setBackgroundColour(colours.black)
- self:setTextColour(colours.white)
- end
- end
- do
- --@require ../Class.lua
- --@require ../Logger.lua
- --@require FrameBuffer.lua
- GuiHost = Class:subClass("GuiHost")
- function GuiHost:init(Page, Document)
- self.Page = Page
- self.Document = Document
- self.Navigation = nil
- local w, h = term.getSize()
- self.Buffer = FrameBuffer:new(w - 1, nil, true)
- self.y=0
- end
- function GuiHost:prepareDraw()
- local current = term.current()
- Debug("Loading")
- self.Buffer:clear()
- term.redirect(self.Buffer:createTable())
- self.Document:draw(self.Buffer)
- term.redirect(current)
- Debug("Loaded")
- end
- --Drawing
- function GuiHost:setY(y)
- local w, h = term.getSize()
- if y >= 0 and y < (self.Buffer.endY - h + 2) then
- self.y = y
- end
- end
- function GuiHost:draw()
- term.setBackgroundColour(colours.black)
- term.clear()
- self:DrawHeader()
- Debug("Drawing start")
- local w, h = term.getSize()
- for i=1, h, 1 do
- term.setCursorPos(1, i+2)
- self.Buffer:drawLine(term, i+self.y)
- end
- Debug("Drawing end")
- end
- function GuiHost:DrawHeader()
- term.setCursorPos(1,1)
- term.setBackgroundColour(colours.red)
- term.setTextColour(colours.white)
- term.write("<")
- term.setBackgroundColour(colours.orange)
- term.write(">")
- term.setBackgroundColour(colours.lightGrey)
- term.write(" " .. self.Page.Url .. string.rep(" ", term.getSize() - #self.Page.Url))
- term.setTextColour(colours.blue)
- term.setCursorPos(1,2)
- term.clearLine()
- term.write(self.Page.Title)
- term.setCursorPos(1, 3)
- term.setBackgroundColour(colours.black)
- term.setTextColour(colours.white)
- end
- function GuiHost:click(x, y)
- if y <= 2 then
- --Can't respond to navigation
- if not self.Navigation then
- return
- end
- --Action in headerbar
- if y == 1 then
- if x == 1 then
- self.Navigation:back()
- elseif x == 2 then
- self.Navigation:forward()
- else
- self:startEdit()
- end
- end
- end
- end
- function GuiHost:startEdit()
- term.setBackgroundColour(colours.lightGrey)
- term.setTextColour(colours.white)
- term.setCursorBlink(true)
- local line = self.Page.Url
- local w = term.getSize()
- local sx = 4
- local pos = #line
- term.setCursorPos(sx + pos, 1)
- local function redraw( customReplaceChar )
- local scroll = 0
- if sx + pos >= w then
- scroll = (sx + pos) - w
- end
- local cx,cy = term.getCursorPos()
- term.setCursorPos( sx, cy )
- local replace = customReplaceChar
- if replace then
- term.write( string.rep( replace, math.max( string.len(line) - scroll, 0 ) ) )
- else
- term.write( string.sub( line, scroll + 1 ) )
- end
- term.setCursorPos( sx + pos - scroll, cy )
- end
- while true do
- local event, param = os.pullEvent()
- if event == "char" then
- -- Typed key
- line = string.sub( line, 1, pos ) .. param .. string.sub( line, pos + 1 )
- pos = pos + 1
- redraw()
- elseif event == "paste" then
- -- Pasted text
- line = string.sub( line, 1, pos ) .. param .. string.sub( line, pos + 1 )
- pos = pos + string.len( param )
- redraw()
- elseif event == "key" then
- if param == keys.enter then
- -- Enter
- break
- elseif param == 1 then
- --Escape
- term.setCursorBlink(false)
- return
- elseif param == keys.left then
- -- Left
- if pos > 0 then
- pos = pos - 1
- redraw()
- end
- elseif param == keys.right then
- -- Right
- if pos < string.len(line) then
- redraw(" ")
- pos = pos + 1
- redraw()
- end
- elseif param == keys.backspace then
- -- Backspace
- if pos > 0 then
- redraw(" ")
- line = string.sub( line, 1, pos - 1 ) .. string.sub( line, pos + 1 )
- pos = pos - 1
- redraw()
- end
- elseif param == keys.home then
- -- Home
- redraw(" ")
- pos = 0
- redraw()
- elseif param == keys.delete then
- -- Delete
- if pos < string.len(line) then
- redraw(" ")
- line = string.sub( line, 1, pos ) .. string.sub( line, pos + 2 )
- redraw()
- end
- elseif param == keys["end"] then
- -- End
- redraw(" ")
- pos = string.len(line)
- redraw()
- end
- elseif event == "term_resize" then
- -- Terminal resized
- w = term.getSize()
- redraw()
- end
- end
- term.setCursorBlink(false)
- self.Navigation:load(line)
- end
- end
- do
- --@require ../Class.lua
- --@require ../Logger.lua
- --@require Elements/Element.lua
- --@require SlaXml.lua
- --@require TagRegister.lua
- DomTreeParser = Class:subClass("DomTreeParser")
- local Push, Pop = table.insert, table.remove
- function DomTreeParser.Parse(Xml, ThisPage)
- local Document = {
- ['Type'] = 'document',
- ['Tag'] = '#document',
- ['Attributes'] = {}, --Should have none but prevents breaking it
- ['Children'] = {},
- }
- local Current = Document
- local Children = Current.Children
- local Stack = { Document }
- local Parser = SLAXML:parser({
- -- When "<foo" or <x:foo is seen
- startElement = function(Tag, nsURI, nsPrefix)
- Tag = Tag:lower()
- local El = {
- ['Type'] = "element",
- ['Tag'] = Tag,
- ['Children'] = {},
- --['El'] = {}, -- (Only tags)
- ['Attributes'] = {},
- ['NsURI'] = nsURI,
- }
- if Current==Document then
- if Document.Root then
- Error(("Encountered element '%s' when the document already has a root '%s' element"):format(Tag, Document.Root.Name))
- end
- Document.Root = El
- end
- Current = El
- Push(Stack,El)
- end,
- -- attribute found on current element
- attribute = function(Name, Value, nsURI, nsPrefix)
- Name = Name:lower()
- if not Current or Current.Type~="element" then
- Error(("Encountered an attribute %s=%s but it wasn't inside an element"):format(Name,Value))
- end
- if nsURI ~= nil then
- Current.Attributes[nsURI .. ':' .. Name] = Value
- else
- Current.Attributes[Name] = Value
- end
- end,
- -- When "</foo>" or </x:foo> or "/>" is seen
- closeElement = function(Tag, nsURI)
- Tag = Tag:lower()
- if Current.Tag~=Tag or Current.Type~="element" then
- Error(("Received a close element notification for '%s' but was inside a '%s' %s"):format(Tag, Current.Tag, Current.Type))
- end
- local El = Current
- Pop(Stack)
- Current = Stack[#Stack]
- Push(Current.Children, TagRegister.CreateElement(El, ThisPage))
- end,
- -- text and CDATA nodes
- text = function(Text)
- if Current.Type~='document' then
- if Current.Type~="element" then
- Error(("Received a text notification '%s' but was inside a %s"):format(Text, Current.Type))
- end
- Push(Current.Children, TextElement:new({
- ['Type']='text',
- ['Tag']='#text',
- ['Value']=Text,
- }, ThisPage))
- end
- end,
- })
- -- Ignore whitespace-only text nodes and strip leading/trailing whitespace from text
- -- (does not strip leading/trailing whitespace from CDATA)
- Parser:parse(Xml, {stripWhitespace=true})
- return DocumentElement:new(Document)
- end
- end
- do
- --@require ../TagRegister.lua
- --@require Element.lua
- --Base class for HTML elements inside the dom tree
- HtmlElement = Element:subClass("HtmlElement")
- TagRegister.SetDefaultClass(HtmlElement)
- function HtmlElement:init(Data, Page)
- self:super(Element).init(Data, Page)
- self.Children = Data.Children
- self.Attributes = Data.Attributes
- local Id = self.Attributes["id"]
- if Id ~= nil and Id ~= "" then
- Page.Ids[Id] = self
- end
- for _, Child in ipairs(self.Children) do
- Child:setParent(self)
- end
- end
- function HtmlElement:innerHTML()
- local Out = ''
- for _, Child in ipairs(self.Children) do
- Out = Out .. Child:toString()
- end
- return Out
- end
- function HtmlElement:toString()
- local Out = '<'..self.Tag
- for Key,Value in pairs(self.Attributes) do
- Out = Out .. ' ' .. Key ..'="'..Value..'"'
- end
- Out = Out .. '>'
- Out = Out .. self:innerHTML()
- return Out.. '</'..self.Tag..'>'
- end
- function HtmlElement:subClass(name, tag)
- local instance = self:super(Element).subClass(name)
- if tag then
- TagRegister.Register(tag, instance)
- end
- return instance
- end
- function HtmlElement:height()
- local Height = 0
- for _, Child in pairs(self.Children) do
- Height = Height + Child.height()
- end
- return Height
- end
- function HtmlElement:draw(Gui)
- for _, Child in ipairs(self.Children) do
- Child:draw(Gui)
- end
- end
- function HtmlElement:stripWhitespace()
- for _,Child in ipairs(self.Children) do
- Child:stripWhitespace()
- if Child.Type == "text" and (Child.Contents == "" or Child.Contents == nil) then
- table.remove(self.Children, _)
- end
- end
- end
- --Root document
- DocumentElement = Element:subClass("DocumentElement")
- function DocumentElement:init(Data, Page)
- self:super(Element).init(Data, Page)
- self.Children = Data.Children
- self.Html = nil
- self.Head = nil
- self.Body = nil
- for _, Child in pairs(self.Children) do
- if Child.Tag == "html" then
- self.Html = Child
- self.Head = Child.Head
- self.Body = Child.Body
- end
- end
- end
- function DocumentElement:draw(Buffer)
- self.Body:draw(Buffer)
- end
- function DocumentElement:stripWhitespace()
- self.Body:stripWhitespace()
- end
- end
- do
- --@require HtmlElement.lua
- --The <html> tag
- HtmlTagElement = HtmlElement:subClass("HtmlTagElement", "html")
- function HtmlTagElement:init(Data, Page)
- self:super(HtmlElement).init(Data, Page)
- self.Head = nil
- self.Body = nil
- for _, Child in pairs(self.Children) do
- if Child.Tag == "head" then
- self.Head = Child
- elseif Child.Tag == "body" then
- self.Body = Child
- end
- end
- end
- --The <head> tag
- HeadElement = HtmlElement:subClass("HeadElement", "head")
- function HeadElement:width()
- return 0
- end
- function HeadElement:height()
- return 0
- end
- --The <title tag
- TitleElement = HtmlElement:subClass("TitleElement", "title")
- function TitleElement:init(Data, Page)
- self:super(HtmlElement).init(Data, Page)
- Page.Title = self:innerHTML()
- end
- end
- do
- --@require Class.lua
- --@require Html/DomTree.lua
- --@require Gui/GuiHelper.lua
- --@require Page.lua
- local ErrorPage = [[
- <html>
- <head>
- <title>Error</title>
- </head>
- <body>
- <h1>Sorry, could not access that page</h1>
- </body>
- </html>
- ]]
- local StartPage = [[
- <html>
- <head>
- <title>About BobCat</title>
- </head>
- <body>
- <h1>Hai!</h1>
- </body>
- </html>
- ]]
- Navigation = Class:subClass("Navigation")
- function Navigation:init()
- self.History = { "about:new"}
- self.HistoryIndex = 1
- self.Page = Page:new("about:new")
- self.Document = DomTreeParser.Parse(StartPage, self.Page)
- self.Gui = GuiHost:new(self.Page, self.Document)
- self.Gui.Navigation = self
- self.Gui:prepareDraw()
- end
- function Navigation:load(Url, ...)
- Debug("Going to page %s", Url)
- if Url:sub(0, 7) == "file://" then
- self:loadLocal(Url:sub(8), ...)
- elseif Url == "about:new" then
- self:_loadAbout()
- else
- self:loadRemote(Url, ...)
- end
- end
- function Navigation:loadRemote(Url, PostData, Headers, AddToNav)
- self.Page = Page:new(Url)
- local Result = http.post(Url, PostData, Headers)
- if not Result then
- self:_loadDocument(ErrorPage)
- else
- self:_loadDocument(Result:readAll())
- if AddToNav ~= false then
- self:_addNavigation(Url)
- end
- end
- end
- function Navigation:loadLocal(File, AddToNav)
- local Url = "file://"..tostring(File)
- self.Page = Page:new(Url)
- local Result = fs.open(File, "r")
- if not Result then
- self:_loadDocument(ErrorPage)
- else
- self:_loadDocument(Result.readAll())
- if AddToNav ~= false then
- self:_addNavigation(Url)
- end
- end
- end
- function Navigation:_loadAbout()
- self.Page = Page:new("about:new")
- self:_loadDocument(StartPage)
- end
- function Navigation:_addNavigation(Url)
- self.HistoryIndex = self.HistoryIndex + 1
- local length = #self.History
- if length > self.HistoryIndex then
- for i = self.HistoryIndex, length, 1 do
- table.remove(self.History, i)
- end
- end
- table.insert(self.History, Url)
- end
- function Navigation:_loadDocument(Contents)
- self.Document=DomTreeParser.Parse(Contents, self.Page)
- if not self.Document then
- Error("Could not load document")
- end
- self.Document:stripWhitespace()
- self.Gui.Page = self.Page
- self.Gui.Document = self.Document
- Debug("Gui Draw from Navigation")
- self.Gui:prepareDraw()
- end
- function Navigation:back()
- if self.HistoryIndex > 1 then
- self.HistoryIndex = self.HistoryIndex - 1
- self:refresh()
- end
- end
- function Navigation:forward()
- if self.HistoryIndex < #self.History then
- self.HistoryIndex = self.HistoryIndex + 1
- self:refresh()
- end
- end
- function Navigation:refresh()
- local Url = self.History[self.HistoryIndex]
- if Url:sub(0, 7) == "file://" then
- self:loadLocal(Url:sub(8), false)
- elseif Url == "about:new" then
- self:_loadAbout()
- else
- self:loadRemote(Url, nil, nil, false)
- end
- end
- end
- do
- --@require HtmlElement.lua
- --@require ../../Logger.lua
- --The <body> tag
- BodyElement = HtmlElement:subClass("BodyElement", "body")
- BlockElement = HtmlElement:subClass("BlockElement")
- function BlockElement:init(...)
- self:super(HtmlElement).init(...)
- self.PaddingTop = 1
- self.PaddingBottom = 1
- end
- function BlockElement:draw(Buffer)
- local x, y = Buffer:getCursorPos()
- if x ~= 1 then
- Buffer:setCursorPos(1, y + self.PaddingTop)
- else
- Buffer:setCursorPos(1, y + self.PaddingTop - 1)
- end
- self:blockDraw(Buffer)
- local x, y = Buffer:getCursorPos()
- Buffer:setCursorPos(1, y+self.PaddingBottom)
- end
- function BlockElement:blockDraw(buffer) self:super(HtmlElement).draw(Buffer) end
- HeaderOne = BlockElement:subClass("HeaderOne", "h1")
- function HeaderOne:init(...)
- self:super(BlockElement).init(...)
- self.PaddingTop = 2
- end
- function HeaderOne:blockDraw(Buffer)
- local col = Buffer:getColours()
- Buffer:setBackgroundColour(colours.blue)
- Buffer:setTextColour(colours.yellow)
- self:super(BlockElement).blockDraw(Buffer)
- Buffer:restoreColours(col)
- end
- HeaderTwo = BlockElement:subClass("HeaderTwo", "h2")
- function HeaderTwo:blockDraw(Buffer)
- local col = Buffer:getColours()
- Buffer:setBackgroundColour(colours.cyan)
- Buffer:setTextColour(colours.orange)
- self:super(BlockElement).blockDraw(Buffer)
- Buffer:restoreColours(col)
- end
- Link = HtmlElement:subClass("Link", "a")
- function Link:init(Data, Page)
- self:super(HtmlElement).init(Data, Page)
- self.Link = self.Attributes.href
- end
- function Link:draw(Buffer)
- local col = Buffer:getColours()
- Buffer:setTextColour(colours.lime)
- self:super(HtmlElement).draw(Buffer)
- Buffer:restoreColours(col)
- end
- Script = HtmlElement:subClass("Script", "script")
- function Script:draw(Buffer) end
- Paragraph = BlockElement:subClass("Paragraph", "p")
- end
- --@require Html/Elements/Element.lua
- --@require Html/Elements/HtmlElement.lua
- --@require Html/Elements/HeadElements.lua
- --@require Html/Elements/BodyElements.lua
- --@require Html/DomTree.lua
- --@require Page.lua
- --@require Navigation.lua
Advertisement
Add Comment
Please, Sign In to add comment