Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parser crashes on entry-hyperlinks #8

Closed
czaefferer opened this issue Sep 29, 2020 · 4 comments
Closed

Parser crashes on entry-hyperlinks #8

czaefferer opened this issue Sep 29, 2020 · 4 comments

Comments

@czaefferer
Copy link
Contributor

The readme says inline hyperlinks to entries are not supported (entry-hyperlinks/ENTRY_HYPERLINK), however the parser even crashes when it encounters these. Is it possible to have the parser just ignore these as it does with embedded assets for example?

Below is an example JSON generated by Contentful, my Dart file to parse the JSON (it contains the same JSON just encoded), and the log of the crash.

{
  "nodeType": "document",
  "data": {},
  "content": [
    {
      "nodeType": "paragraph",
      "content": [
        {
          "nodeType": "text",
          "value": "text before",
          "marks": [],
          "data": {}
        }
      ],
      "data": {}
    },
    {
      "nodeType": "paragraph",
      "content": [
        {
          "nodeType": "text",
          "value": "",
          "marks": [],
          "data": {}
        },
        {
          "nodeType": "entry-hyperlink",
          "content": [
            {
              "nodeType": "text",
              "value": "my content link",
              "marks": [],
              "data": {}
            }
          ],
          "data": {
            "target": {
              "sys": {
                "id": "6eiOBFjSlQukBFMx4XtzE",
                "type": "Link",
                "linkType": "Entry"
              }
            }
          }
        },
        {
          "nodeType": "text",
          "value": "",
          "marks": [],
          "data": {}
        }
      ],
      "data": {}
    },
    {
      "nodeType": "paragraph",
      "content": [
        {
          "nodeType": "text",
          "value": "text after",
          "marks": [],
          "data": {}
        }
      ],
      "data": {}
    }
  ]
}
import 'dart:convert';

import 'package:contentful_rich_text/contentful_rich_text.dart';
import 'package:flutter/material.dart';

class LinkErrorScreen extends StatelessWidget {
  const LinkErrorScreen({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    dynamic document = jsonDecode("{\r\n  \"nodeType\": \"document\",\r\n  \"data\": {},\r\n  \"content\": [\r\n    {\r\n      \"nodeType\": \"paragraph\",\r\n      \"content\": [\r\n        {\r\n          \"nodeType\": \"text\",\r\n          \"value\": \"text before\",\r\n          \"marks\": [],\r\n          \"data\": {}\r\n        }\r\n      ],\r\n      \"data\": {}\r\n    },\r\n    {\r\n      \"nodeType\": \"paragraph\",\r\n      \"content\": [\r\n        {\r\n          \"nodeType\": \"text\",\r\n          \"value\": \"\",\r\n          \"marks\": [],\r\n          \"data\": {}\r\n        },\r\n        {\r\n          \"nodeType\": \"entry-hyperlink\",\r\n          \"content\": [\r\n            {\r\n              \"nodeType\": \"text\",\r\n              \"value\": \"my content link\",\r\n              \"marks\": [],\r\n              \"data\": {}\r\n            }\r\n          ],\r\n          \"data\": {\r\n            \"target\": {\r\n              \"sys\": {\r\n                \"id\": \"6eiOBFjSlQukBFMx4XtzE\",\r\n                \"type\": \"Link\",\r\n                \"linkType\": \"Entry\"\r\n              }\r\n            }\r\n          }\r\n        },\r\n        {\r\n          \"nodeType\": \"text\",\r\n          \"value\": \"\",\r\n          \"marks\": [],\r\n          \"data\": {}\r\n        }\r\n      ],\r\n      \"data\": {}\r\n    },\r\n    {\r\n      \"nodeType\": \"paragraph\",\r\n      \"content\": [\r\n        {\r\n          \"nodeType\": \"text\",\r\n          \"value\": \"text after\",\r\n          \"marks\": [],\r\n          \"data\": {}\r\n        }\r\n      ],\r\n      \"data\": {}\r\n    }\r\n  ]\r\n}\r\n");
    return ContentfulRichText(document).documentToWidgetTree;
  }
}
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following NoSuchMethodError was thrown building Paragraph(dirty):
The method 'contains' was called on null.
Receiver: null
Tried calling: contains("&")
The relevant error-causing widget was:
  LinkErrorScreen                                                                   lib/pages/home.dart
When the exception was thrown, this was the stack:
#0      Object.noSuchMethod  (dart:core-patch/object_patch.dart:51:5)
#1      HtmlUnescapeBase.convert                                                    package:html_unescape/src/base.dart
#2      ContentfulRichText._processInlineNode                                       package:contentful_rich_text/contentful_rich_text.dart
#3      ContentfulRichText.nodeToWidget.<anonymous closure>.<anonymous closure>     package:contentful_rich_text/contentful_rich_text.dart
#4      MappedListIterable.elementAt  (dart:_internal/iterable.dart:417:31)
#5      ListIterator.moveNext  (dart:_internal/iterable.dart:343:26)
#6      new List.from  (dart:core-patch/array_patch.dart:57:19)
#7      ContentfulRichText.nodeToWidget.<anonymous closure>                         package:contentful_rich_text/contentful_rich_text.dart
#8      Paragraph.build                                                             package:contentful_rich_text/widgets/paragraph.dart
#9      StatelessElement.build                                                      package:flutter/…/widgets/framework.dart
#10     ComponentElement.performRebuild                                             package:flutter/…/widgets/framework.dart
#11     Element.rebuild                                                             package:flutter/…/widgets/framework.dart
#12     ComponentElement._firstBuild                                                package:flutter/…/widgets/framework.dart
#13     ComponentElement.mount                                                      package:flutter/…/widgets/framework.dart
#14     Element.inflateWidget                                                       package:flutter/…/widgets/framework.dart
#15     MultiChildRenderObjectElement.mount                                         package:flutter/…/widgets/framework.dart
...     Normal element mounting (45 frames)
#60     Element.inflateWidget                                                       package:flutter/…/widgets/framework.dart
#61     Element.updateChild                                                         package:flutter/…/widgets/framework.dart
#62     SliverMultiBoxAdaptorElement.updateChild                                    package:flutter/…/widgets/sliver.dart
#63     SliverMultiBoxAdaptorElement.createChild.<anonymous closure>                package:flutter/…/widgets/sliver.dart
#64     BuildOwner.buildScope                                                       package:flutter/…/widgets/framework.dart
#65     SliverMultiBoxAdaptorElement.createChild                                    package:flutter/…/widgets/sliver.dart
#66     RenderSliverMultiBoxAdaptor._createOrObtainChild.<anonymous closure>        package:flutter/…/rendering/sliver_multi_box_adaptor.dart
#67     RenderObject.invokeLayoutCallback.<anonymous closure>                       package:flutter/…/rendering/object.dart
#68     PipelineOwner._enableMutationsToDirtySubtrees                               package:flutter/…/rendering/object.dart
#69     RenderObject.invokeLayoutCallback                                           package:flutter/…/rendering/object.dart
#70     RenderSliverMultiBoxAdaptor._createOrObtainChild                            package:flutter/…/rendering/sliver_multi_box_adaptor.dart
#71     RenderSliverMultiBoxAdaptor.addInitialChild                                 package:flutter/…/rendering/sliver_multi_box_adaptor.dart
#72     RenderSliverFixedExtentBoxAdaptor.performLayout                             package:flutter/…/rendering/sliver_fixed_extent_list.dart
#73     RenderObject.layout                                                         package:flutter/…/rendering/object.dart
#74     RenderSliverEdgeInsetsPadding.performLayout                                 package:flutter/…/rendering/sliver_padding.dart
#75     _RenderSliverFractionalPadding.performLayout                                package:flutter/…/widgets/sliver_fill.dart
#76     RenderObject.layout                                                         package:flutter/…/rendering/object.dart
#77     RenderViewportBase.layoutChildSequence                                      package:flutter/…/rendering/viewport.dart
#78     RenderViewport._attemptLayout                                               package:flutter/…/rendering/viewport.dart
#79     RenderViewport.performLayout                                                package:flutter/…/rendering/viewport.dart
#80     RenderObject._layoutWithoutResize                                           package:flutter/…/rendering/object.dart
#81     PipelineOwner.flushLayout                                                   package:flutter/…/rendering/object.dart
#82     RendererBinding.drawFrame                                                   package:flutter/…/rendering/binding.dart
#83     WidgetsBinding.drawFrame                                                    package:flutter/…/widgets/binding.dart
#84     RendererBinding._handlePersistentFrameCallback                              package:flutter/…/rendering/binding.dart
#85     SchedulerBinding._invokeFrameCallback                                       package:flutter/…/scheduler/binding.dart
#86     SchedulerBinding.handleDrawFrame                                            package:flutter/…/scheduler/binding.dart
#87     SchedulerBinding._handleDrawFrame                                           package:flutter/…/scheduler/binding.dart
#91     _invoke  (dart:ui/hooks.dart:253:10)
#92     _drawFrame  (dart:ui/hooks.dart:211:3)
(elided 3 frames from dart:async)
════════════════════════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building Paragraph(dirty):
The method 'contains' was called on null.
Receiver: null
Tried calling: contains("&")

The relevant error-causing widget was
    LinkErrorScreen                                                                 lib/pages/home.dart
When the exception was thrown, this was the stack
#0      Object.noSuchMethod  (dart:core-patch/object_patch.dart:51:5)
#1      HtmlUnescapeBase.convert                                                    package:html_unescape/src/base.dart
#2      ContentfulRichText._processInlineNode                                       package:contentful_rich_text/contentful_rich_text.dart
#3      ContentfulRichText.nodeToWidget.<anonymous closure>.<anonymous closure>     package:contentful_rich_text/contentful_rich_text.dart
#4      MappedListIterable.elementAt  (dart:_internal/iterable.dart:417:31)
...
════════════════════════════════════════════════════════════════════════════════
@localpcguy
Copy link
Collaborator

localpcguy commented Sep 29, 2020

Yup, I would guess that null-checking the textNode.value prior to trying to call HtmlUnescape.convert() on it would potentially resolve the issue.

https://github.com/Kumanu/contentful-rich-text-flutter/blob/master/lib/contentful_rich_text.dart

184    String nodeValue = HtmlUnescape().convert(textNode.value);

Happy to accept a PR that updates that, or I'll try to address it. In the meantime I'd recommend not including the entry link if possible.

@czaefferer
Copy link
Contributor Author

Disabling unsupported types is definitely planned, however I would still prefer if the parser wouldn't crash in case the configuration is off ;)

I'll create a PR with this check:

if (textNode.value == null) return TextSpan();

Would it be ok to also add the following block? That way links to entries would still be ignored, but at least the text would be output. Without it, the above would only output a blank line:

    // for links to entries only process the child-nodes
    if (node['nodeType'] == 'entry-hyperlink') {
      return TextSpan(
        children: node['content']
            .map<TextSpan>((subNode) => _processInlineNode(subNode) as TextSpan)
            .toList(),
      );
    }

@localpcguy
Copy link
Collaborator

Yup, I like the idea of keeping the text 👍

@localpcguy
Copy link
Collaborator

resolved via #9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants