scribe/src/classes/paragraph_converter.cr

94 lines
3.5 KiB
Crystal
Raw Normal View History

First step rendering a page 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.
2021-05-16 20:14:25 +02:00
class ParagraphConverter
include Nodes
def convert(paragraphs : Array(PostResponse::Paragraph)) : Array(Child)
if paragraphs.first?.nil?
return [Empty.new] of Child
else
case paragraphs.first.type
when PostResponse::ParagraphType::BQ
paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = BlockQuote.new(children: children)
when PostResponse::ParagraphType::H3
paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = Heading3.new(children: children)
when PostResponse::ParagraphType::H4
paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = Heading4.new(children: children)
when PostResponse::ParagraphType::IFRAME
paragraph = paragraphs.shift
if iframe = paragraph.iframe
node = IFrame.new(href: iframe.mediaResource.href)
First step rendering a page 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.
2021-05-16 20:14:25 +02:00
else
node = Empty.new
end
when PostResponse::ParagraphType::IMG
paragraph = paragraphs.shift
node = convert_img(paragraph)
First step rendering a page 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.
2021-05-16 20:14:25 +02:00
when PostResponse::ParagraphType::OLI
list_items = convert_oli(paragraphs)
node = OrderedList.new(children: list_items)
when PostResponse::ParagraphType::P
paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = Paragraph.new(children: children)
when PostResponse::ParagraphType::PQ
paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = BlockQuote.new(children: children)
First step rendering a page 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.
2021-05-16 20:14:25 +02:00
when PostResponse::ParagraphType::PRE
paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = Preformatted.new(children: children)
when PostResponse::ParagraphType::ULI
list_items = convert_uli(paragraphs)
node = UnorderedList.new(children: list_items)
else
paragraphs.shift # so we don't recurse infinitely
node = Empty.new
end
[node, convert(paragraphs)].flatten.reject(&.empty?)
end
end
private def convert_uli(paragraphs : Array(PostResponse::Paragraph)) : Array(Child)
if paragraphs.first? && paragraphs.first.type.is_a?(PostResponse::ParagraphType::ULI)
paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups)
[ListItem.new(children: children)] + convert_uli(paragraphs)
else
[] of Child
end
end
private def convert_oli(paragraphs : Array(PostResponse::Paragraph)) : Array(Child)
if paragraphs.first? && paragraphs.first.type.is_a?(PostResponse::ParagraphType::OLI)
paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups)
[ListItem.new(children: children)] + convert_oli(paragraphs)
else
[] of Child
end
end
private def convert_img(paragraph : PostResponse::Paragraph) : Child
if metadata = paragraph.metadata
caption_markup = MarkupConverter.convert(paragraph.text, paragraph.markups)
Figure.new(children: [
Image.new(
src: metadata.id,
originalWidth: metadata.originalWidth,
originalHeight: metadata.originalHeight
),
2021-08-08 20:23:38 +02:00
FigureCaption.new(children: caption_markup),
] of Child)
else
Empty.new
end
end
First step rendering a page 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.
2021-05-16 20:14:25 +02:00
end