Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions content_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const (
ScrollyCopyChildType = "scrolly-copy-child"
ScrollySectionChildType = "scrolly-section-child"
TableChildType = "table-child"

TimelineType = "timeline"
TimelineEventType = "timeline-event"
TimelineEventChildType = "timeline-event-child"
)

var (
Expand Down Expand Up @@ -395,6 +399,7 @@ type BodyBlock struct {
*YoutubeVideo
*CustomCodeComponent
*ClipSet
*Timeline
}

func (n *BodyBlock) GetType() string {
Expand Down Expand Up @@ -459,6 +464,9 @@ func (n *BodyBlock) GetEmbedded() Node {
if n.ClipSet != nil {
return n.ClipSet
}
if n.Timeline != nil {
return n.Timeline
}
return nil
}

Expand Down Expand Up @@ -520,6 +528,9 @@ func (n *BodyBlock) GetChildren() []Node {
if n.ClipSet != nil {
return n.ClipSet.GetChildren()
}
if n.Timeline != nil {
return n.Timeline.GetChildren()
}
return nil
}

Expand Down Expand Up @@ -646,6 +657,12 @@ func (n *BodyBlock) UnmarshalJSON(data []byte) error {
return err
}
n.ClipSet = &v
case TimelineType:
var v Timeline
if err := json.Unmarshal(data, &v); err != nil {
return err
}
n.Timeline = &v
default:
return fmt.Errorf("failed to unmarshal BodyBlock from %s: %w", data, ErrUnmarshalInvalidNode)
}
Expand Down Expand Up @@ -692,6 +709,8 @@ func (n *BodyBlock) MarshalJSON() ([]byte, error) {
return json.Marshal(n.CustomCodeComponent)
case n.ClipSet != nil:
return json.Marshal(n.ClipSet)
case n.Timeline != nil:
return json.Marshal(n.Timeline)
default:
return []byte(`{}`), nil
}
Expand Down Expand Up @@ -738,6 +757,8 @@ func makeBodyBlock(n Node) (*BodyBlock, error) {
return &BodyBlock{CustomCodeComponent: n.(*CustomCodeComponent)}, nil
case ClipSetType:
return &BodyBlock{ClipSet: n.(*ClipSet)}, nil
case TimelineType:
return &BodyBlock{Timeline: n.(*Timeline)}, nil
default:
return nil, ErrInvalidChildType
}
Expand Down Expand Up @@ -2594,3 +2615,143 @@ func (n *Teaser) GetEmbedded() Node {
func (n *Teaser) GetChildren() []Node {
return nil
}

type Timeline struct {
Type string `json:"type"`
Title string `json:"title,omitempty"`
LayoutWidth string `json:"layoutWidth,omitempty"`
Children []*TimelineEvent `json:"children"`
}

func (n *Timeline) GetType() string {
return n.Type
}

func (n *Timeline) GetEmbedded() Node {
return nil
}

func (n *Timeline) GetChildren() []Node {
result := make([]Node, len(n.Children))
for i, v := range n.Children {
result[i] = v
}
return result
}

func (n *Timeline) AppendChild(child Node) error {
if child.GetType() != TimelineEventType {
return ErrInvalidChildType
}
n.Children = append(n.Children, child.(*TimelineEvent))
return nil
}

type TimelineEvent struct {
Type string `json:"type"`
Title string `json:"title,omitempty"`
Children []*TimelineEventChild `json:"children"`
}

func (n *TimelineEvent) GetType() string {
return n.Type
}

func (n *TimelineEvent) GetEmbedded() Node {
return nil
}

func (n *TimelineEvent) GetChildren() []Node {
result := make([]Node, len(n.Children))
for i, v := range n.Children {
result[i] = v
}
return result
}

func (n *TimelineEvent) AppendChild(child Node) error {
c, err := makeTimelineEventChild(child)
if err != nil {
return err
}
n.Children = append(n.Children, c)
return nil
}

type TimelineEventChild struct {
*Paragraph
*ImageSet
}

func (n *TimelineEventChild) GetType() string {
return TimelineEventChildType
}

func (n *TimelineEventChild) GetEmbedded() Node {
if n.Paragraph != nil {
return n.Paragraph
}
if n.ImageSet != nil {
return n.ImageSet
}
return nil
}

func (n *TimelineEventChild) GetChildren() []Node {
if n.Paragraph != nil {
return n.Paragraph.GetChildren()
}
if n.ImageSet != nil {
return n.ImageSet.GetChildren()
}
return nil
}

func (n *TimelineEventChild) AppendChild(child Node) error { return ErrCannotHaveChildren }

func (n *TimelineEventChild) UnmarshalJSON(data []byte) error {
var tn typedNode
if err := json.Unmarshal(data, &tn); err != nil {
return err
}
switch tn.Type {
case ParagraphType:
var v Paragraph
if err := json.Unmarshal(data, &v); err != nil {
return err
}
n.Paragraph = &v
case ImageSetType:
var v ImageSet
if err := json.Unmarshal(data, &v); err != nil {
return err
}
n.ImageSet = &v
default:
return fmt.Errorf("failed to unmarshal TimelineEventChild from %s: %w", data, ErrUnmarshalInvalidNode)
}
return nil
}

func (n *TimelineEventChild) MarshalJSON() ([]byte, error) {
switch {
case n.Paragraph != nil:
return json.Marshal(n.Paragraph)
case n.ImageSet != nil:
return json.Marshal(n.ImageSet)
default:
return []byte(`{}`), nil
}
}

// Build a TimelineEventChild wrapper.
func makeTimelineEventChild(n Node) (*TimelineEventChild, error) {
switch n.GetType() {
case ParagraphType:
return &TimelineEventChild{Paragraph: n.(*Paragraph)}, nil
case ImageSetType:
return &TimelineEventChild{ImageSet: n.(*ImageSet)}, nil
default:
return nil, ErrInvalidChildType
}
}
38 changes: 38 additions & 0 deletions libraries/from-bodyxml/go/html_transformers.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ var defaultTransformers = map[string]transformer{
}
},
"ol": func(ol *etree.Element) contenttree.Node {
dataType := attr(ol, "data-type")
if dataType == "timeline_events" {
return newLiftChildrenNode()
}
return &contenttree.List{
Type: contenttree.ListType,
Ordered: true,
Expand All @@ -174,6 +178,20 @@ var defaultTransformers = map[string]transformer{
}
},
"li": func(li *etree.Element) contenttree.Node {
dataType := attr(li, "data-type")
if dataType == "timeline_event" {
timelineEventTitle := ""
if h4Element := findChild(li, "h4"); h4Element != nil {
timelineEventTitle = textContent(h4Element)
//extract title but don't treat like a child element
li.RemoveChild(h4Element)
}
return &contenttree.TimelineEvent{
Type: contenttree.TimelineEventType,
Title: timelineEventTitle,
Children: []*contenttree.TimelineEventChild{},
}
}
return &contenttree.ListItem{
Type: contenttree.ListItemType,
Children: []*contenttree.ListItemChild{},
Expand Down Expand Up @@ -333,6 +351,26 @@ var defaultTransformers = map[string]transformer{
return newUnknownNode(attr(div, "class"), div)
}
},
"section": func(section *etree.Element) contenttree.Node {
switch attr(section, "data-type") {
case "timeline":
{
timelineTitle := ""
if h3Element := findChild(section, "h3"); h3Element != nil {
timelineTitle = textContent(h3Element)
//extract title but don't treat like a child element
section.RemoveChild(h3Element)
}
return &contenttree.Timeline{
Type: contenttree.TimelineType,
Title: timelineTitle,
LayoutWidth: attr(section, "data-layout-width"),
Children: []*contenttree.TimelineEvent{},
}
}
}
return newUnknownNode("", section)
},
"experimental": func(_ *etree.Element) contenttree.Node {
return newLiftChildrenNode()
},
Expand Down
29 changes: 28 additions & 1 deletion libraries/to-external-bodyxml/go/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,29 @@ func transformNode(n contenttree.Node) (string, error) {
// CCC nodes won't be available in the "external" body XML format.
case *contenttree.CustomCodeComponent:
return "", nil

/*
TODO: Remove comment to integrate timeline.
case *contenttree.Timeline:
{
titleXML := ""
if node.Title != "" {
titleXML = fmt.Sprintf("<h3>%s</h3>", node.Title)
}
layoutWidthXML := ""
if node.LayoutWidth != "" {
layoutWidthXML = fmt.Sprintf("data-layout-width=\"%s\"", node.LayoutWidth)
}
return fmt.Sprintf("<section data-type=\"timeline\" %s>%s<ol data-type=\"timeline_events\">%s</ol></section>", layoutWidthXML, titleXML, innerXML), nil
}
case *contenttree.TimelineEvent:
{
titleXML := ""
if node.Title != "" {
titleXML = fmt.Sprintf("<h4>%s</h4>", node.Title)
}
return fmt.Sprintf("<li data-type=\"timeline_event\">%s%s</li>", titleXML, innerXML), nil
}
*/
// content tree nodes which require transformation of their embedded nodes
case *contenttree.BodyBlock:
return transformNode(n.GetEmbedded())
Expand All @@ -275,6 +297,11 @@ func transformNode(n contenttree.Node) (string, error) {
return transformNode(n.GetEmbedded())
case *contenttree.TableChild:
return transformNode(n.GetEmbedded())
/*
TODO: Remove comment to integrate timeline.
case *contenttree.TimelineEventChild:
return transformNode(n.GetEmbedded())
*/
}

return "", nil
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<body><section data-type="timeline" data-layout-width="full-width"><h3>lorem</h3><ol data-type="timeline_events"><li data-type="timeline_event"><h4>ipsum</h4><p>lorem</p><p>ipsum</p><content type="http://www.ft.com/ontology/content/ImageSet" id="a47cfe4c-1f80-4492-9717-93aa65565e37"/></li><li data-type="timeline_event"><h4>lorem</h4><p>ipsum</p></li></ol></section></body>
59 changes: 59 additions & 0 deletions tests/bodyxml-to-content-tree/output/simple-body-section.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"type": "root",
"body": {
"type": "body",
"children": [
{
"type": "timeline",
"title": "lorem",
"layoutWidth": "full-width",
"children": [
{
"type": "timeline-event",
"title": "ipsum",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"value": "lorem"
}
]
},
{
"type": "paragraph",
"children": [
{
"type": "text",
"value": "ipsum"
}
]
},
{
"type": "image-set",
"id": "a47cfe4c-1f80-4492-9717-93aa65565e37"
}
]
},
{
"type": "timeline-event",
"title": "lorem",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"value": "ipsum"
}
]
}
]
}
]
}
],
"version": 1
}
}
Loading