The API responds with a bunch of paragraphs which the client converts into Paragraph objects. This turns the paragraphs in a PostResponse's Paragraph objects into the form needed to render them on a page. This includes converting flat list elements into list elements nested by a UL. And adding a limited markups along the way. The array of paragraphs is passed to a recursive function. The function takes the first paragraph and either wraps the (marked up) contents in a container tag (like Paragraph or Heading3), and then moves onto the next tag. If it finds a list, it starts parsing the next paragraphs as a list instead. Originally, this was implemented like so: ```crystal paragraph = paragraphs.shift if list? convert_list([paragraph] + paragraphs) end ``` However, passing the `paragraphs` after adding it to the already shifted `paragraph` creates a new object. This means `paragraphs` won't be mutated and once the list is parsed, it starts with the next element of the list. Instead, the element is `shift`ed inside each converter. ```crystal if paragraphs.first == list? convert_list(paragraphs) end def convert_list(paragraphs) paragraph = paragraphs.shift # ... end ``` When rendering, there is an Empty and Container object. These represent a kind of "null object" for both leafs and parent objects respectively. They should never actually render. Emptys are filtered out, and Containers are never created explicitly but this will make the types pass. IFrames are a bit of a special case. Each IFrame has custom data on it that this system would need to be aware of. For now, instead of trying to parse the seemingly large number of iframe variations and dealing with embedded iframe problems, this will just keep track of the source page URL and send the user there with a link.
74 lines
2.1 KiB
Crystal
74 lines
2.1 KiB
Crystal
struct StringSplit
|
|
getter pre, content, post
|
|
|
|
def initialize(@pre : String, @content : String, @post : String)
|
|
end
|
|
end
|
|
|
|
class MarkupConverter
|
|
include Nodes
|
|
|
|
getter markups : Array(PostResponse::Markup)
|
|
getter text : String
|
|
|
|
def self.convert(text : String, markups : Array(PostResponse::Markup))
|
|
new(text, markups).convert
|
|
end
|
|
|
|
def initialize(@text : String, @markups : Array(PostResponse::Markup))
|
|
end
|
|
|
|
def convert : Array(Child)
|
|
if markups.empty?
|
|
return [Text.new(content: text)] of Child
|
|
end
|
|
offset = 0
|
|
text_splits = markups.reduce([text]) do |splits, markup|
|
|
individual_split = split_string(markup.start - offset, markup.end - offset, splits.pop)
|
|
offset = markup.end
|
|
splits.push(individual_split.pre)
|
|
splits.push(individual_split.content)
|
|
splits.push(individual_split.post)
|
|
end
|
|
text_splits.in_groups_of(2, "").map_with_index do |split, index|
|
|
plain, to_be_marked = split
|
|
markup = markups.fetch(index, Text.new(""))
|
|
if markup.is_a?(Text)
|
|
[Text.new(plain)] of Child
|
|
else
|
|
case markup.type
|
|
when PostResponse::MarkupType::CODE
|
|
container = construct_markup(to_be_marked, Code)
|
|
when PostResponse::MarkupType::EM
|
|
container = construct_markup(to_be_marked, Emphasis)
|
|
when PostResponse::MarkupType::STRONG
|
|
container = construct_markup(to_be_marked, Strong)
|
|
else
|
|
container = construct_markup(to_be_marked, Code)
|
|
end
|
|
[Text.new(plain), container] of Child
|
|
end
|
|
end.flatten.reject(&.empty?)
|
|
end
|
|
|
|
private def construct_markup(text : String, container : Container.class) : Child
|
|
container.new(children: [Text.new(content: text)] of Child)
|
|
end
|
|
|
|
private def split_string(start : Int32, finish : Int32, string : String)
|
|
if start.zero?
|
|
pre = ""
|
|
else
|
|
pre = string[0...start]
|
|
end
|
|
|
|
if finish == string.size
|
|
post = ""
|
|
else
|
|
post = string[finish...string.size]
|
|
end
|
|
|
|
content = string[start...finish]
|
|
StringSplit.new(pre, content, post)
|
|
end
|
|
end
|