· 5 years ago · Oct 26, 2020, 11:34 PM
1do
2 Object = { __name = "Object" }
3
4 function Object:init(...) end
5
6 function Object:super(class)
7 local obj = self
8 return setmetatable({ }, { __index = function(t, k)
9 return function(...)
10 while class do
11 local method = class[k]
12 if method then
13 return method(obj, ...)
14 end
15 class = class.__super
16 end
17 end
18 end })
19 end
20 function Object:createTable()
21 local obj = self
22 local class = obj:getClass()
23 return setmetatable({ }, { __index = function(t, k)
24 return function(...)
25 local method = class[k]
26 return method(obj, ...)
27 end
28 end })
29 end
30
31 function Object:getSuper()
32 return rawget(self:getClass(), "__super")
33 end
34
35 function Object:getClass()
36 return rawget(self, "__class")
37 end
38
39 function Object:instanceof(targetType)
40 local objectType = self:getClass()
41 while objectType do
42 if targetType == objectType then
43 return true
44 end
45 objectType = objectType:getSuper()
46 end
47 return false
48 end
49
50 function Object:toString()
51 return self:getClass():getName().." ("..tostring(self):sub(8, -1)..")"
52 end
53
54 function Object:extend(obj)
55 self.__index = self -- This is a metatable
56
57 local obj = obj or {}
58 obj.__super = self
59 local obj = setmetatable(obj, self)
60 return obj
61 end
62
63 Class = Object:extend({__name = "Class"})
64 function Class:new(...)
65 local instance = { __class = self }
66 self:extend(instance)
67 instance:init(...)
68 return instance
69 end
70
71 function Class:getName(name)
72 return self.__name
73 end
74 function Class:subClass(name)
75 local instance = { }
76 instance.__name = name
77
78 return self:extend(instance)
79 end
80end
81do
82 local log = fs.open("BobCat.log", "w")
83
84
85 function Debug(Message, ...)
86 Log("DEBUG", Message, ...)
87 end
88 function Print(Message, ...)
89 print(Log("PRINT", Message, ...))
90 end
91 function PrintError(Message, ...)
92 printError(Log("WARNING", Message, ...))
93 end
94 function Error(Message, ...)
95 error(Log("ERROR", Message, ...))
96 end
97
98 function Log(Type, Message, ...)
99 local str = string.format(tostring(Message), unpack({...}))
100 log.writeLine("["..Type.."] "..str)
101 log.flush()
102
103 return str
104 end
105end
106do
107 --[=====================================================================[
108 v0.6 Copyright © 2013-2014 Gavin Kistner <!@phrogz.net>; MIT Licensed
109 See http://github.com/Phrogz/SLAXML for details.
110 --]=====================================================================]
111 SLAXML = {
112 VERSION = "0.6",
113 _call = {
114 pi = function(target,content)
115 print(string.format("<?%s %s?>",target,content))
116 end,
117 comment = function(content)
118 print(string.format("<!-- %s -->",content))
119 end,
120 startElement = function(name,nsURI,nsPrefix)
121 io.write("<")
122 if nsPrefix then io.write(nsPrefix,":") end
123 io.write(name)
124 if nsURI then io.write(" (ns='",nsURI,"')") end
125 print(">")
126 end,
127 attribute = function(name,value,nsURI,nsPrefix)
128 io.write(' ')
129 if nsPrefix then io.write(nsPrefix,":") end
130 io.write(name,'=',string.format('%q',value))
131 if nsURI then io.write(" (ns='",nsURI,"')") end
132 io.write("\n")
133 end,
134 text = function(text)
135 print(string.format(" text: %q",text))
136 end,
137 closeElement = function(name,nsURI,nsPrefix)
138 print(string.format("</%s>",name))
139 end,
140 }
141 }
142
143 function SLAXML:parser(callbacks)
144 return { _call=callbacks or self._call, parse=SLAXML.parse }
145 end
146
147 function SLAXML:parse(xml,options)
148 if not options then options = { stripWhitespace=false } end
149
150 -- Cache references for maximum speed
151 local find, sub, gsub, char, push, pop = string.find, string.sub, string.gsub, string.char, table.insert, table.remove
152 local first, last, match1, match2, match3, pos2, nsURI
153 local unpack = unpack or table.unpack
154 local pos = 1
155 local state = "text"
156 local textStart = 1
157 local currentElement={}
158 local currentAttributes={}
159 local currentAttributeCt -- manually track length since the table is re-used
160 local nsStack = {}
161
162 local entityMap = { ["lt"]="<", ["gt"]=">", ["amp"]="&", ["quot"]='"', ["apos"]="'" }
163 local entitySwap = function(orig,n,s) return entityMap[s] or n=="#" and char(s) or orig end
164 local function unescape(str) return gsub( str, '(&(#?)([%d%a]+);)', entitySwap ) end
165 local anyElement = false
166
167 local function finishText()
168 if first>textStart and self._call.text then
169 local text = sub(xml,textStart,first-1)
170 if options.stripWhitespace then
171 text = gsub(text,'^%s+','')
172 text = gsub(text,'%s+$','')
173 if #text==0 then text=nil end
174 end
175 if text then self._call.text(unescape(text)) end
176 end
177 end
178
179 local function findPI()
180 first, last, match1, match2 = find( xml, '^<%?([:%a_][:%w_.-]*) ?(.-)%?>', pos )
181 if first then
182 finishText()
183 if self._call.pi then self._call.pi(match1,match2) end
184 pos = last+1
185 textStart = pos
186 return true
187 end
188 end
189
190 local function findComment()
191 first, last, match1 = find( xml, '^<!%-%-(.-)%-%->', pos )
192 if first then
193 finishText()
194 if self._call.comment then self._call.comment(match1) end
195 pos = last+1
196 textStart = pos
197 return true
198 end
199 end
200
201 local function nsForPrefix(prefix)
202 if prefix=='xml' then return 'http://www.w3.org/XML/1998/namespace' end -- http://www.w3.org/TR/xml-names/#ns-decl
203 for i=#nsStack,1,-1 do if nsStack[i][prefix] then return nsStack[i][prefix] end end
204 error(("Cannot find namespace for prefix %s"):format(prefix))
205 end
206
207 local function startElement()
208 anyElement = true
209 first, last, match1 = find( xml, '^<([%a_][%w_.-]*)', pos )
210 if first then
211 currentElement[2] = nil -- reset the nsURI, since this table is re-used
212 currentElement[3] = nil -- reset the nsPrefix, since this table is re-used
213 finishText()
214 pos = last+1
215 first,last,match2 = find(xml, '^:([%a_][%w_.-]*)', pos )
216 if first then
217 currentElement[1] = match2
218 currentElement[3] = match1 -- Save the prefix for later resolution
219 match1 = match2
220 pos = last+1
221 else
222 currentElement[1] = match1
223 for i=#nsStack,1,-1 do if nsStack[i]['!'] then currentElement[2] = nsStack[i]['!']; break end end
224 end
225 currentAttributeCt = 0
226 push(nsStack,{})
227 return true
228 end
229 end
230
231 local function findAttribute()
232 first, last, match1 = find( xml, '^%s+([:%a_][:%w_.-]*)%s*=%s*', pos )
233 if first then
234 pos2 = last+1
235 first, last, match2 = find( xml, '^"([^<"]*)"', pos2 ) -- FIXME: disallow non-entity ampersands
236 if first then
237 pos = last+1
238 match2 = unescape(match2)
239 else
240 first, last, match2 = find( xml, "^'([^<']*)'", pos2 ) -- FIXME: disallow non-entity ampersands
241 if first then
242 pos = last+1
243 match2 = unescape(match2)
244 end
245 end
246 end
247 if match1 and match2 then
248 local currentAttribute = {match1,match2}
249 local prefix,name = string.match(match1,'^([^:]+):([^:]+)$')
250 if prefix then
251 if prefix=='xmlns' then
252 nsStack[#nsStack][name] = match2
253 else
254 currentAttribute[1] = name
255 currentAttribute[4] = prefix
256 end
257 else
258 if match1=='xmlns' then
259 nsStack[#nsStack]['!'] = match2
260 currentElement[2] = match2
261 end
262 end
263 currentAttributeCt = currentAttributeCt + 1
264 currentAttributes[currentAttributeCt] = currentAttribute
265 return true
266 end
267 end
268
269 local function findCDATA()
270 first, last, match1 = find( xml, '^<!%[CDATA%[(.-)%]%]>', pos )
271 if first then
272 finishText()
273 if self._call.text then self._call.text(match1) end
274 pos = last+1
275 textStart = pos
276 return true
277 end
278 end
279
280 local function closeElement()
281 first, last, match1 = find( xml, '^%s*(/?)>', pos )
282 if first then
283 state = "text"
284 pos = last+1
285 textStart = pos
286
287 -- Resolve namespace prefixes AFTER all new/redefined prefixes have been parsed
288 if currentElement[3] then currentElement[2] = nsForPrefix(currentElement[3]) end
289 if self._call.startElement then self._call.startElement(unpack(currentElement)) end
290 if self._call.attribute then
291 for i=1,currentAttributeCt do
292 if currentAttributes[i][4] then currentAttributes[i][3] = nsForPrefix(currentAttributes[i][4]) end
293 self._call.attribute(unpack(currentAttributes[i]))
294 end
295 end
296
297 if match1=="/" then
298 pop(nsStack)
299 if self._call.closeElement then self._call.closeElement(unpack(currentElement)) end
300 end
301 return true
302 end
303 end
304
305 local function findElementClose()
306 first, last, match1, match2 = find( xml, '^</([%a_][%w_.-]*)%s*>', pos )
307 if first then
308 nsURI = nil
309 for i=#nsStack,1,-1 do if nsStack[i]['!'] then nsURI = nsStack[i]['!']; break end end
310 else
311 first, last, match2, match1 = find( xml, '^</([%a_][%w_.-]*):([%a_][%w_.-]*)%s*>', pos )
312 if first then nsURI = nsForPrefix(match2) end
313 end
314 if first then
315 finishText()
316 if self._call.closeElement then self._call.closeElement(match1,nsURI) end
317 pos = last+1
318 textStart = pos
319 pop(nsStack)
320 return true
321 end
322 end
323
324 while pos<#xml do
325 if state=="text" then
326 if not (findPI() or findComment() or findCDATA() or findElementClose()) then
327 if startElement() then
328 state = "attributes"
329 else
330 first, last = find( xml, '^[^<]+', pos )
331 pos = (first and last or pos) + 1
332 end
333 end
334 elseif state=="attributes" then
335 if not findAttribute() then
336 if not closeElement() then
337 print(state)
338 error("Was in an element and couldn't find attributes or the close.")
339 end
340 end
341 end
342 end
343
344 if not anyElement then error("Parsing did not discover any elements") end
345 if #nsStack > 0 then error("Parsing ended with unclosed elements") end
346 end
347end
348do
349 --@require Class.lua
350
351 --Base class for pages
352 Page = Class:subClass("Page")
353 function Page:init(Url)
354 self.Url = Url
355 self.Title = Url
356
357 self.Ids = { }
358 self.Clicks = { }
359 end
360end
361do
362 --@require ../../Class.lua
363
364 --Base class for elements inside the DOM tree
365 Element = Class:subClass("Element")
366 function Element:init(Data, Page)
367 self.Type = Data.Type
368 self.Tag = Data.Tag
369
370 self.Page = Page
371
372 self.Parent = nil
373 end
374
375 function Element:setParent(Parent)
376 self.Parent = Parent
377 end
378
379 function Element:draw(Gui) end
380 function Element:stripWhitespace() end
381
382 function Element:previousElement()
383 local previous = nil
384 for _, Child in pairs(self.Parent.Children) do
385 if Child == self then
386 break
387 end
388
389 previous = Child
390 end
391 return previous
392 end
393
394 function Element:nextElement()
395 local nextElement = nil
396 for _, Child in pairs(self.Parent.Children) do
397 if nextElement then
398 break
399 end
400
401 if Child == self then
402 nextElement = true
403 end
404 end
405 return nextElement
406 end
407
408
409 --Text elements
410 TextElement = Element:subClass("TextElement")
411 function TextElement:init(Data, Page)
412 self:super(Element).init(Data, Page)
413
414 self.Contents = Data.Value or ""
415 end
416 function TextElement:toString()
417 return self.Contents
418 end
419
420 function TextElement:draw(Buffer)
421 --Buffer:print(self.Contents)
422 write(self.Contents)
423 end
424
425 function TextElement:stripWhitespace()
426 self.Contents = self.Contents:gsub("%s+", " ")
427 end
428end
429do
430 --@require ../Class.lua
431 --@require ../Logger.lua
432
433 local Tags = { }
434 local DefaultClass = nil
435
436 TagRegister = Class:subClass("TagRegister")
437 function TagRegister.Register(tag, class)
438 Debug("Registering %s for tag %s", class:getName(), tag)
439 Tags[tag] = class
440 end
441
442 function TagRegister.SetDefaultClass(class)
443 DefaultClass = class
444 end
445
446 function TagRegister.CreateElement(Data, ThisPage)
447 local EClass = Tags[Data.Tag]
448 if EClass == nil then
449 EClass = DefaultClass
450 end
451 return EClass:new(Data, ThisPage)
452 end
453end
454do
455 --@require ../Class.lua
456 --From Lyqyd's FrameBuffer API (http://pastebin.com/Aaza6h5v)
457
458 FrameBuffer = Class:subClass("FrameBuffer")
459 function FrameBuffer:init(sizeX, sizeY, colour)
460 self.text = {}
461 self.textColor = {}
462 self.backColor = {}
463 self.cursorX = 1
464 self.cursorY = 1
465 self.curTextColor = "0"
466 self.curBackColor = "f"
467 self.sizeX = sizeX or 51
468 self.sizeY = sizeY or -1
469 self.endY = sizeY or 0
470 if colour == nil then colour = true end
471 self.color = colour
472
473 self:createLine()
474 end
475
476 function FrameBuffer:write(text)
477 text = tostring(text)
478
479 --Debug("Writing %s (%s, %s)", text, self.curTextColor, self.curBackColor)
480 local pos = self.cursorX
481 if (self.cursorY > self.sizeY and self.sizeY ~= -1) or self.cursorY < 1 then
482 self.cursorX = pos + #text
483 return
484
485 end
486
487 if pos + #text <= 1 then
488 --skip entirely.
489 self.cursorX = pos + #text
490 return
491 elseif pos < 1 then
492 --adjust text to fit on screen starting at one.
493 writeText = string.sub(text, math.abs(self.cursorX) + 2)
494 self.cursorX = 1
495 elseif pos > self.sizeX then
496 --if we're off the edge to the right, skip entirely.
497 self.cursorX = pos + #text
498 return
499 else
500 writeText = text
501 end
502
503 local lineText = self.text[self.cursorY]
504 local lineColor = self.textColor[self.cursorY]
505 local lineBack = self.backColor[self.cursorY]
506 local preStop = self.cursorX - 1
507 local preStart = math.min(1, preStop)
508 local postStart = self.cursorX + string.len(writeText)
509 local postStop = self.sizeX
510 self.text[self.cursorY] = string.sub(lineText, preStart, preStop)..writeText..string.sub(lineText, postStart, postStop)
511 self.textColor[self.cursorY] = string.sub(lineColor, preStart, preStop)..string.rep(self.curTextColor, #writeText)..string.sub(lineColor, postStart, postStop)
512 self.backColor[self.cursorY] = string.sub(lineBack, preStart, preStop)..string.rep(self.curBackColor, #writeText)..string.sub(lineBack, postStart, postStop)
513 self.cursorX = pos + string.len(text)
514 end
515
516 function FrameBuffer:createLine(line)
517 if line==nil then
518 self.endY = self.endY + 1
519 line = self.endY
520 elseif line > self.endY then
521 self.endY = line
522 end
523
524 --Debug("Creating line at: %s", line)
525
526 self.text[line] = string.rep(" ", self.sizeX)
527 self.textColor[line] = string.rep(self.curTextColor, self.sizeX)
528 self.backColor[line] = string.rep(self.curBackColor, self.sizeX)
529 end
530
531 function FrameBuffer:clearLine()
532 self:createLine(self.cursorY)
533 end
534
535 function FrameBuffer:clear()
536 self.text = {}
537 self.textColor = {}
538 self.backColor = {}
539 self.cursorX = 1
540 self.cursorY = 1
541 self.curTextColor = "0"
542 self.curBackColor = "f"
543 self.endY = self.sizeY or 0
544 end
545
546 function FrameBuffer:getCursorPos()
547 return self.cursorX, self.cursorY
548 end
549 function FrameBuffer:setCursorPos(x, y)
550 self.cursorX = math.floor(tonumber(x)) or self.cursorX
551 self.cursorY = math.floor(tonumber(y)) or self.cursorY
552
553
554 --Debug("Cursor to %s, %s", x,y)
555
556 if not self.text[y] then
557 local last = nil
558 for i = self.endY+1, y,1 do
559 if last ~= i then
560 self:createLine(i)
561 last = i
562 end
563 end
564 end
565 end
566 function FrameBuffer:setCursorBlink(b) end
567 function FrameBuffer:scroll(n)
568 self.cursorX = self.cursorX + n
569 end
570
571 function FrameBuffer:getSize()
572 local y = self.sizeY
573 if y == -1 then
574 y = self.endY + 1000
575 end
576 return self.sizeX, y
577 end
578
579 function FrameBuffer:setTextColor(clr)
580 if clr and clr <= 32768 and clr >= 1 then
581 if self.color then
582 self.curTextColor = string.format("%x", math.floor(math.log(clr) / math.log(2)))
583 elseif clr == 1 or clr == 32768 then
584 self.curTextColor = string.format("%x", math.floor(math.log(clr) / math.log(2)))
585 else
586 return nil, "Colour not supported"
587 end
588 end
589 end
590 FrameBuffer.setTextColour = FrameBuffer.setTextColor
591 function FrameBuffer:setBackgroundColor(clr)
592 if clr and clr <= 32768 and clr >= 1 then
593 if self.color then
594 self.curBackColor = string.format("%x", math.floor(math.log(clr) / math.log(2)))
595 elseif clr == 32768 or clr == 1 then
596 self.curBackColor = string.format("%x", math.floor(math.log(clr) / math.log(2)))
597 else
598 return nil, "Colour not supported"
599 end
600 end
601 end
602 FrameBuffer.setBackgroundColour = FrameBuffer.setBackgroundColor
603 function FrameBuffer:isColor()
604 return self.color == true
605 end
606 FrameBuffer.isColour = FrameBuffer.isColor
607
608 function FrameBuffer:draw(redirect)
609 local x, y = redirect.getCursorPos()
610
611 for p=1, self.endY do
612 self:drawLine(redirect, p)
613 end
614 redirect.setCursorPos(x + self.cursorX, y + self.cursorY)
615 redirect.setTextColor(2 ^ tonumber(self.curTextColor, 16))
616 redirect.setBackgroundColor(2 ^ tonumber(self.curBackColor, 16))
617 end
618
619 function FrameBuffer:drawLine(redirect, line)
620 local x, y = redirect.getCursorPos()
621 local w, h = redirect.getSize()
622
623 local thisY = line + y
624 --redirect.setCursorPos(x, thisY)
625
626 --If off the edge
627 if not self.text[line] then
628 return
629 end
630
631 local lineEnd = false
632 local offset = x
633 while not lineEnd do
634 local textColorString = string.match(string.sub(self.textColor[line], offset), string.sub(self.textColor[line], offset, offset).."*")
635 local backColorString = string.match(string.sub(self.backColor[line], offset), string.sub(self.backColor[line], offset, offset).."*")
636
637 redirect.setTextColor(2 ^ tonumber(string.sub(textColorString, 1, 1), 16))
638 redirect.setBackgroundColor(2 ^ tonumber(string.sub(backColorString, 1, 1), 16))
639 redirect.write(string.sub(self.text[line], offset, offset + math.min(#textColorString, #backColorString) - 1))
640 offset = offset + math.min(#textColorString, #backColorString)
641
642 if offset > self.sizeX then lineEnd = true end
643 end
644 end
645
646 function FrameBuffer:getColours()
647 return {2 ^ tonumber(string.sub(self.curTextColor, 1, 1), 16), 2 ^ tonumber(string.sub(self.curBackColor, 1, 1), 16)}
648 end
649
650 function FrameBuffer:restoreColours(colours)
651 self:setTextColour(colours[1])
652 self:setBackgroundColour(colours[2])
653 end
654
655 function FrameBuffer:resetColours()
656 self:setBackgroundColour(colours.black)
657 self:setTextColour(colours.white)
658 end
659end
660do
661 --@require ../Class.lua
662 --@require ../Logger.lua
663 --@require FrameBuffer.lua
664
665 GuiHost = Class:subClass("GuiHost")
666
667 function GuiHost:init(Page, Document)
668 self.Page = Page
669 self.Document = Document
670
671 self.Navigation = nil
672
673 local w, h = term.getSize()
674 self.Buffer = FrameBuffer:new(w - 1, nil, true)
675
676 self.y=0
677 end
678
679 function GuiHost:prepareDraw()
680 local current = term.current()
681 Debug("Loading")
682 self.Buffer:clear()
683 term.redirect(self.Buffer:createTable())
684
685 self.Document:draw(self.Buffer)
686 term.redirect(current)
687 Debug("Loaded")
688 end
689
690 --Drawing
691 function GuiHost:setY(y)
692 local w, h = term.getSize()
693 if y >= 0 and y < (self.Buffer.endY - h + 2) then
694 self.y = y
695 end
696 end
697
698 function GuiHost:draw()
699 term.setBackgroundColour(colours.black)
700 term.clear()
701 self:DrawHeader()
702 Debug("Drawing start")
703
704 local w, h = term.getSize()
705
706 for i=1, h, 1 do
707 term.setCursorPos(1, i+2)
708 self.Buffer:drawLine(term, i+self.y)
709 end
710 Debug("Drawing end")
711 end
712
713 function GuiHost:DrawHeader()
714 term.setCursorPos(1,1)
715
716 term.setBackgroundColour(colours.red)
717 term.setTextColour(colours.white)
718 term.write("<")
719
720 term.setBackgroundColour(colours.orange)
721 term.write(">")
722
723 term.setBackgroundColour(colours.lightGrey)
724
725 term.write(" " .. self.Page.Url .. string.rep(" ", term.getSize() - #self.Page.Url))
726
727 term.setTextColour(colours.blue)
728
729 term.setCursorPos(1,2)
730 term.clearLine()
731 term.write(self.Page.Title)
732
733 term.setCursorPos(1, 3)
734 term.setBackgroundColour(colours.black)
735 term.setTextColour(colours.white)
736 end
737
738 function GuiHost:click(x, y)
739 if y <= 2 then
740 --Can't respond to navigation
741 if not self.Navigation then
742 return
743 end
744 --Action in headerbar
745 if y == 1 then
746 if x == 1 then
747 self.Navigation:back()
748 elseif x == 2 then
749 self.Navigation:forward()
750 else
751 self:startEdit()
752 end
753 end
754 end
755 end
756
757 function GuiHost:startEdit()
758
759 term.setBackgroundColour(colours.lightGrey)
760 term.setTextColour(colours.white)
761 term.setCursorBlink(true)
762
763 local line = self.Page.Url
764
765 local w = term.getSize()
766 local sx = 4
767 local pos = #line
768
769 term.setCursorPos(sx + pos, 1)
770
771 local function redraw( customReplaceChar )
772 local scroll = 0
773 if sx + pos >= w then
774 scroll = (sx + pos) - w
775 end
776
777 local cx,cy = term.getCursorPos()
778 term.setCursorPos( sx, cy )
779 local replace = customReplaceChar
780 if replace then
781 term.write( string.rep( replace, math.max( string.len(line) - scroll, 0 ) ) )
782 else
783 term.write( string.sub( line, scroll + 1 ) )
784 end
785 term.setCursorPos( sx + pos - scroll, cy )
786 end
787
788 while true do
789 local event, param = os.pullEvent()
790 if event == "char" then
791 -- Typed key
792 line = string.sub( line, 1, pos ) .. param .. string.sub( line, pos + 1 )
793 pos = pos + 1
794 redraw()
795
796 elseif event == "paste" then
797 -- Pasted text
798 line = string.sub( line, 1, pos ) .. param .. string.sub( line, pos + 1 )
799 pos = pos + string.len( param )
800 redraw()
801
802 elseif event == "key" then
803 if param == keys.enter then
804 -- Enter
805 break
806 elseif param == 1 then
807 --Escape
808 term.setCursorBlink(false)
809 return
810
811 elseif param == keys.left then
812 -- Left
813 if pos > 0 then
814 pos = pos - 1
815 redraw()
816 end
817
818 elseif param == keys.right then
819 -- Right
820 if pos < string.len(line) then
821 redraw(" ")
822 pos = pos + 1
823 redraw()
824 end
825 elseif param == keys.backspace then
826 -- Backspace
827 if pos > 0 then
828 redraw(" ")
829 line = string.sub( line, 1, pos - 1 ) .. string.sub( line, pos + 1 )
830 pos = pos - 1
831 redraw()
832 end
833 elseif param == keys.home then
834 -- Home
835 redraw(" ")
836 pos = 0
837 redraw()
838 elseif param == keys.delete then
839 -- Delete
840 if pos < string.len(line) then
841 redraw(" ")
842 line = string.sub( line, 1, pos ) .. string.sub( line, pos + 2 )
843 redraw()
844 end
845 elseif param == keys["end"] then
846 -- End
847 redraw(" ")
848 pos = string.len(line)
849 redraw()
850 end
851
852 elseif event == "term_resize" then
853 -- Terminal resized
854 w = term.getSize()
855 redraw()
856
857 end
858 end
859
860 term.setCursorBlink(false)
861
862 self.Navigation:load(line)
863 end
864end
865do
866 --@require ../Class.lua
867 --@require ../Logger.lua
868 --@require Elements/Element.lua
869 --@require SlaXml.lua
870 --@require TagRegister.lua
871
872 DomTreeParser = Class:subClass("DomTreeParser")
873
874 local Push, Pop = table.insert, table.remove
875
876 function DomTreeParser.Parse(Xml, ThisPage)
877 local Document = {
878 ['Type'] = 'document',
879 ['Tag'] = '#document',
880 ['Attributes'] = {}, --Should have none but prevents breaking it
881 ['Children'] = {},
882 }
883
884 local Current = Document
885 local Children = Current.Children
886 local Stack = { Document }
887
888 local Parser = SLAXML:parser({
889
890 -- When "<foo" or <x:foo is seen
891 startElement = function(Tag, nsURI, nsPrefix)
892 Tag = Tag:lower()
893 local El = {
894 ['Type'] = "element",
895 ['Tag'] = Tag,
896 ['Children'] = {},
897 --['El'] = {}, -- (Only tags)
898 ['Attributes'] = {},
899 ['NsURI'] = nsURI,
900 }
901 if Current==Document then
902 if Document.Root then
903 Error(("Encountered element '%s' when the document already has a root '%s' element"):format(Tag, Document.Root.Name))
904 end
905 Document.Root = El
906 end
907
908 Current = El
909 Push(Stack,El)
910 end,
911 -- attribute found on current element
912 attribute = function(Name, Value, nsURI, nsPrefix)
913 Name = Name:lower()
914 if not Current or Current.Type~="element" then
915 Error(("Encountered an attribute %s=%s but it wasn't inside an element"):format(Name,Value))
916 end
917
918 if nsURI ~= nil then
919 Current.Attributes[nsURI .. ':' .. Name] = Value
920 else
921 Current.Attributes[Name] = Value
922 end
923 end,
924 -- When "</foo>" or </x:foo> or "/>" is seen
925 closeElement = function(Tag, nsURI)
926 Tag = Tag:lower()
927
928 if Current.Tag~=Tag or Current.Type~="element" then
929 Error(("Received a close element notification for '%s' but was inside a '%s' %s"):format(Tag, Current.Tag, Current.Type))
930 end
931
932 local El = Current
933
934 Pop(Stack)
935 Current = Stack[#Stack]
936 Push(Current.Children, TagRegister.CreateElement(El, ThisPage))
937 end,
938 -- text and CDATA nodes
939 text = function(Text)
940 if Current.Type~='document' then
941 if Current.Type~="element" then
942 Error(("Received a text notification '%s' but was inside a %s"):format(Text, Current.Type))
943 end
944 Push(Current.Children, TextElement:new({
945 ['Type']='text',
946 ['Tag']='#text',
947 ['Value']=Text,
948 }, ThisPage))
949 end
950 end,
951 })
952
953 -- Ignore whitespace-only text nodes and strip leading/trailing whitespace from text
954 -- (does not strip leading/trailing whitespace from CDATA)
955 Parser:parse(Xml, {stripWhitespace=true})
956
957 return DocumentElement:new(Document)
958 end
959end
960do
961 --@require ../TagRegister.lua
962 --@require Element.lua
963
964 --Base class for HTML elements inside the dom tree
965 HtmlElement = Element:subClass("HtmlElement")
966 TagRegister.SetDefaultClass(HtmlElement)
967
968 function HtmlElement:init(Data, Page)
969 self:super(Element).init(Data, Page)
970
971 self.Children = Data.Children
972 self.Attributes = Data.Attributes
973
974 local Id = self.Attributes["id"]
975 if Id ~= nil and Id ~= "" then
976 Page.Ids[Id] = self
977 end
978
979 for _, Child in ipairs(self.Children) do
980 Child:setParent(self)
981 end
982 end
983 function HtmlElement:innerHTML()
984 local Out = ''
985 for _, Child in ipairs(self.Children) do
986 Out = Out .. Child:toString()
987 end
988
989 return Out
990 end
991 function HtmlElement:toString()
992 local Out = '<'..self.Tag
993 for Key,Value in pairs(self.Attributes) do
994 Out = Out .. ' ' .. Key ..'="'..Value..'"'
995 end
996
997 Out = Out .. '>'
998 Out = Out .. self:innerHTML()
999
1000 return Out.. '</'..self.Tag..'>'
1001 end
1002 function HtmlElement:subClass(name, tag)
1003 local instance = self:super(Element).subClass(name)
1004 if tag then
1005 TagRegister.Register(tag, instance)
1006 end
1007
1008 return instance
1009 end
1010
1011 function HtmlElement:height()
1012 local Height = 0
1013 for _, Child in pairs(self.Children) do
1014 Height = Height + Child.height()
1015 end
1016 return Height
1017 end
1018
1019 function HtmlElement:draw(Gui)
1020 for _, Child in ipairs(self.Children) do
1021 Child:draw(Gui)
1022 end
1023 end
1024
1025 function HtmlElement:stripWhitespace()
1026 for _,Child in ipairs(self.Children) do
1027 Child:stripWhitespace()
1028 if Child.Type == "text" and (Child.Contents == "" or Child.Contents == nil) then
1029 table.remove(self.Children, _)
1030 end
1031 end
1032 end
1033
1034
1035 --Root document
1036 DocumentElement = Element:subClass("DocumentElement")
1037 function DocumentElement:init(Data, Page)
1038 self:super(Element).init(Data, Page)
1039
1040 self.Children = Data.Children
1041
1042 self.Html = nil
1043 self.Head = nil
1044 self.Body = nil
1045 for _, Child in pairs(self.Children) do
1046 if Child.Tag == "html" then
1047 self.Html = Child
1048 self.Head = Child.Head
1049 self.Body = Child.Body
1050 end
1051 end
1052 end
1053
1054 function DocumentElement:draw(Buffer)
1055 self.Body:draw(Buffer)
1056 end
1057
1058 function DocumentElement:stripWhitespace()
1059 self.Body:stripWhitespace()
1060 end
1061end
1062do
1063 --@require HtmlElement.lua
1064
1065 --The <html> tag
1066 HtmlTagElement = HtmlElement:subClass("HtmlTagElement", "html")
1067 function HtmlTagElement:init(Data, Page)
1068 self:super(HtmlElement).init(Data, Page)
1069
1070 self.Head = nil
1071 self.Body = nil
1072 for _, Child in pairs(self.Children) do
1073 if Child.Tag == "head" then
1074 self.Head = Child
1075 elseif Child.Tag == "body" then
1076 self.Body = Child
1077 end
1078 end
1079 end
1080
1081 --The <head> tag
1082 HeadElement = HtmlElement:subClass("HeadElement", "head")
1083 function HeadElement:width()
1084 return 0
1085 end
1086 function HeadElement:height()
1087 return 0
1088 end
1089
1090 --The <title tag
1091 TitleElement = HtmlElement:subClass("TitleElement", "title")
1092 function TitleElement:init(Data, Page)
1093 self:super(HtmlElement).init(Data, Page)
1094 Page.Title = self:innerHTML()
1095 end
1096end
1097do
1098 --@require Class.lua
1099 --@require Html/DomTree.lua
1100 --@require Gui/GuiHelper.lua
1101 --@require Page.lua
1102
1103 local ErrorPage = [[
1104 <html>
1105 <head>
1106 <title>Error</title>
1107 </head>
1108 <body>
1109 <h1>Sorry, could not access that page</h1>
1110 </body>
1111 </html>
1112 ]]
1113
1114 local StartPage = [[
1115 <html>
1116 <head>
1117 <title>About BobCat</title>
1118 </head>
1119 <body>
1120 <h1>Hai!</h1>
1121 </body>
1122 </html>
1123 ]]
1124
1125 Navigation = Class:subClass("Navigation")
1126 function Navigation:init()
1127 self.History = { "about:new"}
1128 self.HistoryIndex = 1
1129
1130 self.Page = Page:new("about:new")
1131 self.Document = DomTreeParser.Parse(StartPage, self.Page)
1132
1133 self.Gui = GuiHost:new(self.Page, self.Document)
1134 self.Gui.Navigation = self
1135 self.Gui:prepareDraw()
1136 end
1137
1138 function Navigation:load(Url, ...)
1139 Debug("Going to page %s", Url)
1140
1141 if Url:sub(0, 7) == "file://" then
1142 self:loadLocal(Url:sub(8), ...)
1143 elseif Url == "about:new" then
1144 self:_loadAbout()
1145 else
1146 self:loadRemote(Url, ...)
1147 end
1148 end
1149
1150 function Navigation:loadRemote(Url, PostData, Headers, AddToNav)
1151 self.Page = Page:new(Url)
1152
1153 local Result = http.post(Url, PostData, Headers)
1154 if not Result then
1155 self:_loadDocument(ErrorPage)
1156 else
1157 self:_loadDocument(Result:readAll())
1158 if AddToNav ~= false then
1159 self:_addNavigation(Url)
1160 end
1161 end
1162 end
1163
1164 function Navigation:loadLocal(File, AddToNav)
1165 local Url = "file://"..tostring(File)
1166 self.Page = Page:new(Url)
1167
1168 local Result = fs.open(File, "r")
1169 if not Result then
1170 self:_loadDocument(ErrorPage)
1171 else
1172 self:_loadDocument(Result.readAll())
1173
1174 if AddToNav ~= false then
1175 self:_addNavigation(Url)
1176 end
1177 end
1178 end
1179
1180 function Navigation:_loadAbout()
1181 self.Page = Page:new("about:new")
1182 self:_loadDocument(StartPage)
1183 end
1184
1185 function Navigation:_addNavigation(Url)
1186 self.HistoryIndex = self.HistoryIndex + 1
1187
1188 local length = #self.History
1189 if length > self.HistoryIndex then
1190 for i = self.HistoryIndex, length, 1 do
1191 table.remove(self.History, i)
1192 end
1193 end
1194
1195 table.insert(self.History, Url)
1196 end
1197
1198 function Navigation:_loadDocument(Contents)
1199 self.Document=DomTreeParser.Parse(Contents, self.Page)
1200
1201 if not self.Document then
1202 Error("Could not load document")
1203 end
1204
1205 self.Document:stripWhitespace()
1206
1207 self.Gui.Page = self.Page
1208 self.Gui.Document = self.Document
1209
1210 Debug("Gui Draw from Navigation")
1211 self.Gui:prepareDraw()
1212 end
1213
1214 function Navigation:back()
1215 if self.HistoryIndex > 1 then
1216 self.HistoryIndex = self.HistoryIndex - 1
1217
1218 self:refresh()
1219 end
1220 end
1221
1222 function Navigation:forward()
1223 if self.HistoryIndex < #self.History then
1224 self.HistoryIndex = self.HistoryIndex + 1
1225
1226 self:refresh()
1227 end
1228 end
1229
1230 function Navigation:refresh()
1231 local Url = self.History[self.HistoryIndex]
1232
1233 if Url:sub(0, 7) == "file://" then
1234 self:loadLocal(Url:sub(8), false)
1235 elseif Url == "about:new" then
1236 self:_loadAbout()
1237 else
1238 self:loadRemote(Url, nil, nil, false)
1239 end
1240 end
1241end
1242do
1243 --@require HtmlElement.lua
1244 --@require ../../Logger.lua
1245
1246
1247 --The <body> tag
1248 BodyElement = HtmlElement:subClass("BodyElement", "body")
1249
1250 BlockElement = HtmlElement:subClass("BlockElement")
1251 function BlockElement:init(...)
1252 self:super(HtmlElement).init(...)
1253
1254 self.PaddingTop = 1
1255 self.PaddingBottom = 1
1256 end
1257 function BlockElement:draw(Buffer)
1258 local x, y = Buffer:getCursorPos()
1259
1260 if x ~= 1 then
1261 Buffer:setCursorPos(1, y + self.PaddingTop)
1262 else
1263 Buffer:setCursorPos(1, y + self.PaddingTop - 1)
1264 end
1265
1266 self:blockDraw(Buffer)
1267
1268 local x, y = Buffer:getCursorPos()
1269 Buffer:setCursorPos(1, y+self.PaddingBottom)
1270 end
1271 function BlockElement:blockDraw(buffer) self:super(HtmlElement).draw(Buffer) end
1272
1273 HeaderOne = BlockElement:subClass("HeaderOne", "h1")
1274 function HeaderOne:init(...)
1275 self:super(BlockElement).init(...)
1276
1277 self.PaddingTop = 2
1278 end
1279 function HeaderOne:blockDraw(Buffer)
1280 local col = Buffer:getColours()
1281
1282 Buffer:setBackgroundColour(colours.blue)
1283 Buffer:setTextColour(colours.yellow)
1284
1285 self:super(BlockElement).blockDraw(Buffer)
1286
1287 Buffer:restoreColours(col)
1288 end
1289
1290 HeaderTwo = BlockElement:subClass("HeaderTwo", "h2")
1291 function HeaderTwo:blockDraw(Buffer)
1292 local col = Buffer:getColours()
1293
1294 Buffer:setBackgroundColour(colours.cyan)
1295 Buffer:setTextColour(colours.orange)
1296
1297 self:super(BlockElement).blockDraw(Buffer)
1298
1299 Buffer:restoreColours(col)
1300 end
1301
1302
1303 Link = HtmlElement:subClass("Link", "a")
1304 function Link:init(Data, Page)
1305 self:super(HtmlElement).init(Data, Page)
1306
1307 self.Link = self.Attributes.href
1308 end
1309
1310 function Link:draw(Buffer)
1311 local col = Buffer:getColours()
1312
1313 Buffer:setTextColour(colours.lime)
1314
1315 self:super(HtmlElement).draw(Buffer)
1316
1317 Buffer:restoreColours(col)
1318 end
1319
1320 Script = HtmlElement:subClass("Script", "script")
1321 function Script:draw(Buffer) end
1322
1323 Paragraph = BlockElement:subClass("Paragraph", "p")
1324end
1325--@require Html/Elements/Element.lua
1326--@require Html/Elements/HtmlElement.lua
1327--@require Html/Elements/HeadElements.lua
1328--@require Html/Elements/BodyElements.lua
1329
1330--@require Html/DomTree.lua
1331--@require Page.lua
1332
1333--@require Navigation.lua