Skip to content

Commit af7397a

Browse files
Merge pull request #119 from Financial-Times/feature/UPPSF-6510
add timeline section to content-tree Go representation
2 parents 326ca79 + 9162689 commit af7397a

File tree

5 files changed

+287
-1
lines changed

5 files changed

+287
-1
lines changed

content_tree.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ const (
7676
ScrollyCopyChildType = "scrolly-copy-child"
7777
ScrollySectionChildType = "scrolly-section-child"
7878
TableChildType = "table-child"
79+
80+
TimelineType = "timeline"
81+
TimelineEventType = "timeline-event"
82+
TimelineEventChildType = "timeline-event-child"
7983
)
8084

8185
var (
@@ -395,6 +399,7 @@ type BodyBlock struct {
395399
*YoutubeVideo
396400
*CustomCodeComponent
397401
*ClipSet
402+
*Timeline
398403
}
399404

400405
func (n *BodyBlock) GetType() string {
@@ -459,6 +464,9 @@ func (n *BodyBlock) GetEmbedded() Node {
459464
if n.ClipSet != nil {
460465
return n.ClipSet
461466
}
467+
if n.Timeline != nil {
468+
return n.Timeline
469+
}
462470
return nil
463471
}
464472

@@ -520,6 +528,9 @@ func (n *BodyBlock) GetChildren() []Node {
520528
if n.ClipSet != nil {
521529
return n.ClipSet.GetChildren()
522530
}
531+
if n.Timeline != nil {
532+
return n.Timeline.GetChildren()
533+
}
523534
return nil
524535
}
525536

@@ -646,6 +657,12 @@ func (n *BodyBlock) UnmarshalJSON(data []byte) error {
646657
return err
647658
}
648659
n.ClipSet = &v
660+
case TimelineType:
661+
var v Timeline
662+
if err := json.Unmarshal(data, &v); err != nil {
663+
return err
664+
}
665+
n.Timeline = &v
649666
default:
650667
return fmt.Errorf("failed to unmarshal BodyBlock from %s: %w", data, ErrUnmarshalInvalidNode)
651668
}
@@ -692,6 +709,8 @@ func (n *BodyBlock) MarshalJSON() ([]byte, error) {
692709
return json.Marshal(n.CustomCodeComponent)
693710
case n.ClipSet != nil:
694711
return json.Marshal(n.ClipSet)
712+
case n.Timeline != nil:
713+
return json.Marshal(n.Timeline)
695714
default:
696715
return []byte(`{}`), nil
697716
}
@@ -738,6 +757,8 @@ func makeBodyBlock(n Node) (*BodyBlock, error) {
738757
return &BodyBlock{CustomCodeComponent: n.(*CustomCodeComponent)}, nil
739758
case ClipSetType:
740759
return &BodyBlock{ClipSet: n.(*ClipSet)}, nil
760+
case TimelineType:
761+
return &BodyBlock{Timeline: n.(*Timeline)}, nil
741762
default:
742763
return nil, ErrInvalidChildType
743764
}
@@ -2594,3 +2615,143 @@ func (n *Teaser) GetEmbedded() Node {
25942615
func (n *Teaser) GetChildren() []Node {
25952616
return nil
25962617
}
2618+
2619+
type Timeline struct {
2620+
Type string `json:"type"`
2621+
Title string `json:"title,omitempty"`
2622+
LayoutWidth string `json:"layoutWidth,omitempty"`
2623+
Children []*TimelineEvent `json:"children"`
2624+
}
2625+
2626+
func (n *Timeline) GetType() string {
2627+
return n.Type
2628+
}
2629+
2630+
func (n *Timeline) GetEmbedded() Node {
2631+
return nil
2632+
}
2633+
2634+
func (n *Timeline) GetChildren() []Node {
2635+
result := make([]Node, len(n.Children))
2636+
for i, v := range n.Children {
2637+
result[i] = v
2638+
}
2639+
return result
2640+
}
2641+
2642+
func (n *Timeline) AppendChild(child Node) error {
2643+
if child.GetType() != TimelineEventType {
2644+
return ErrInvalidChildType
2645+
}
2646+
n.Children = append(n.Children, child.(*TimelineEvent))
2647+
return nil
2648+
}
2649+
2650+
type TimelineEvent struct {
2651+
Type string `json:"type"`
2652+
Title string `json:"title,omitempty"`
2653+
Children []*TimelineEventChild `json:"children"`
2654+
}
2655+
2656+
func (n *TimelineEvent) GetType() string {
2657+
return n.Type
2658+
}
2659+
2660+
func (n *TimelineEvent) GetEmbedded() Node {
2661+
return nil
2662+
}
2663+
2664+
func (n *TimelineEvent) GetChildren() []Node {
2665+
result := make([]Node, len(n.Children))
2666+
for i, v := range n.Children {
2667+
result[i] = v
2668+
}
2669+
return result
2670+
}
2671+
2672+
func (n *TimelineEvent) AppendChild(child Node) error {
2673+
c, err := makeTimelineEventChild(child)
2674+
if err != nil {
2675+
return err
2676+
}
2677+
n.Children = append(n.Children, c)
2678+
return nil
2679+
}
2680+
2681+
type TimelineEventChild struct {
2682+
*Paragraph
2683+
*ImageSet
2684+
}
2685+
2686+
func (n *TimelineEventChild) GetType() string {
2687+
return TimelineEventChildType
2688+
}
2689+
2690+
func (n *TimelineEventChild) GetEmbedded() Node {
2691+
if n.Paragraph != nil {
2692+
return n.Paragraph
2693+
}
2694+
if n.ImageSet != nil {
2695+
return n.ImageSet
2696+
}
2697+
return nil
2698+
}
2699+
2700+
func (n *TimelineEventChild) GetChildren() []Node {
2701+
if n.Paragraph != nil {
2702+
return n.Paragraph.GetChildren()
2703+
}
2704+
if n.ImageSet != nil {
2705+
return n.ImageSet.GetChildren()
2706+
}
2707+
return nil
2708+
}
2709+
2710+
func (n *TimelineEventChild) AppendChild(child Node) error { return ErrCannotHaveChildren }
2711+
2712+
func (n *TimelineEventChild) UnmarshalJSON(data []byte) error {
2713+
var tn typedNode
2714+
if err := json.Unmarshal(data, &tn); err != nil {
2715+
return err
2716+
}
2717+
switch tn.Type {
2718+
case ParagraphType:
2719+
var v Paragraph
2720+
if err := json.Unmarshal(data, &v); err != nil {
2721+
return err
2722+
}
2723+
n.Paragraph = &v
2724+
case ImageSetType:
2725+
var v ImageSet
2726+
if err := json.Unmarshal(data, &v); err != nil {
2727+
return err
2728+
}
2729+
n.ImageSet = &v
2730+
default:
2731+
return fmt.Errorf("failed to unmarshal TimelineEventChild from %s: %w", data, ErrUnmarshalInvalidNode)
2732+
}
2733+
return nil
2734+
}
2735+
2736+
func (n *TimelineEventChild) MarshalJSON() ([]byte, error) {
2737+
switch {
2738+
case n.Paragraph != nil:
2739+
return json.Marshal(n.Paragraph)
2740+
case n.ImageSet != nil:
2741+
return json.Marshal(n.ImageSet)
2742+
default:
2743+
return []byte(`{}`), nil
2744+
}
2745+
}
2746+
2747+
// Build a TimelineEventChild wrapper.
2748+
func makeTimelineEventChild(n Node) (*TimelineEventChild, error) {
2749+
switch n.GetType() {
2750+
case ParagraphType:
2751+
return &TimelineEventChild{Paragraph: n.(*Paragraph)}, nil
2752+
case ImageSetType:
2753+
return &TimelineEventChild{ImageSet: n.(*ImageSet)}, nil
2754+
default:
2755+
return nil, ErrInvalidChildType
2756+
}
2757+
}

libraries/from-bodyxml/go/html_transformers.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ var defaultTransformers = map[string]transformer{
160160
}
161161
},
162162
"ol": func(ol *etree.Element) contenttree.Node {
163+
dataType := attr(ol, "data-type")
164+
if dataType == "timeline_events" {
165+
return newLiftChildrenNode()
166+
}
163167
return &contenttree.List{
164168
Type: contenttree.ListType,
165169
Ordered: true,
@@ -174,6 +178,20 @@ var defaultTransformers = map[string]transformer{
174178
}
175179
},
176180
"li": func(li *etree.Element) contenttree.Node {
181+
dataType := attr(li, "data-type")
182+
if dataType == "timeline_event" {
183+
timelineEventTitle := ""
184+
if h4Element := findChild(li, "h4"); h4Element != nil {
185+
timelineEventTitle = textContent(h4Element)
186+
//extract title but don't treat like a child element
187+
li.RemoveChild(h4Element)
188+
}
189+
return &contenttree.TimelineEvent{
190+
Type: contenttree.TimelineEventType,
191+
Title: timelineEventTitle,
192+
Children: []*contenttree.TimelineEventChild{},
193+
}
194+
}
177195
return &contenttree.ListItem{
178196
Type: contenttree.ListItemType,
179197
Children: []*contenttree.ListItemChild{},
@@ -333,6 +351,26 @@ var defaultTransformers = map[string]transformer{
333351
return newUnknownNode(attr(div, "class"), div)
334352
}
335353
},
354+
"section": func(section *etree.Element) contenttree.Node {
355+
switch attr(section, "data-type") {
356+
case "timeline":
357+
{
358+
timelineTitle := ""
359+
if h3Element := findChild(section, "h3"); h3Element != nil {
360+
timelineTitle = textContent(h3Element)
361+
//extract title but don't treat like a child element
362+
section.RemoveChild(h3Element)
363+
}
364+
return &contenttree.Timeline{
365+
Type: contenttree.TimelineType,
366+
Title: timelineTitle,
367+
LayoutWidth: attr(section, "data-layout-width"),
368+
Children: []*contenttree.TimelineEvent{},
369+
}
370+
}
371+
}
372+
return newUnknownNode("", section)
373+
},
336374
"experimental": func(_ *etree.Element) contenttree.Node {
337375
return newLiftChildrenNode()
338376
},

libraries/to-external-bodyxml/go/transform.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,29 @@ func transformNode(n contenttree.Node) (string, error) {
255255
// CCC nodes won't be available in the "external" body XML format.
256256
case *contenttree.CustomCodeComponent:
257257
return "", nil
258-
258+
/*
259+
TODO: Remove comment to integrate timeline.
260+
case *contenttree.Timeline:
261+
{
262+
titleXML := ""
263+
if node.Title != "" {
264+
titleXML = fmt.Sprintf("<h3>%s</h3>", node.Title)
265+
}
266+
layoutWidthXML := ""
267+
if node.LayoutWidth != "" {
268+
layoutWidthXML = fmt.Sprintf("data-layout-width=\"%s\"", node.LayoutWidth)
269+
}
270+
return fmt.Sprintf("<section data-type=\"timeline\" %s>%s<ol data-type=\"timeline_events\">%s</ol></section>", layoutWidthXML, titleXML, innerXML), nil
271+
}
272+
case *contenttree.TimelineEvent:
273+
{
274+
titleXML := ""
275+
if node.Title != "" {
276+
titleXML = fmt.Sprintf("<h4>%s</h4>", node.Title)
277+
}
278+
return fmt.Sprintf("<li data-type=\"timeline_event\">%s%s</li>", titleXML, innerXML), nil
279+
}
280+
*/
259281
// content tree nodes which require transformation of their embedded nodes
260282
case *contenttree.BodyBlock:
261283
return transformNode(n.GetEmbedded())
@@ -275,6 +297,11 @@ func transformNode(n contenttree.Node) (string, error) {
275297
return transformNode(n.GetEmbedded())
276298
case *contenttree.TableChild:
277299
return transformNode(n.GetEmbedded())
300+
/*
301+
TODO: Remove comment to integrate timeline.
302+
case *contenttree.TimelineEventChild:
303+
return transformNode(n.GetEmbedded())
304+
*/
278305
}
279306

280307
return "", nil
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"type": "root",
3+
"body": {
4+
"type": "body",
5+
"children": [
6+
{
7+
"type": "timeline",
8+
"title": "lorem",
9+
"layoutWidth": "full-width",
10+
"children": [
11+
{
12+
"type": "timeline-event",
13+
"title": "ipsum",
14+
"children": [
15+
{
16+
"type": "paragraph",
17+
"children": [
18+
{
19+
"type": "text",
20+
"value": "lorem"
21+
}
22+
]
23+
},
24+
{
25+
"type": "paragraph",
26+
"children": [
27+
{
28+
"type": "text",
29+
"value": "ipsum"
30+
}
31+
]
32+
},
33+
{
34+
"type": "image-set",
35+
"id": "a47cfe4c-1f80-4492-9717-93aa65565e37"
36+
}
37+
]
38+
},
39+
{
40+
"type": "timeline-event",
41+
"title": "lorem",
42+
"children": [
43+
{
44+
"type": "paragraph",
45+
"children": [
46+
{
47+
"type": "text",
48+
"value": "ipsum"
49+
}
50+
]
51+
}
52+
]
53+
}
54+
]
55+
}
56+
],
57+
"version": 1
58+
}
59+
}

0 commit comments

Comments
 (0)