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

Feature: Nested Matchers #81

Open
fbartho opened this issue Jun 18, 2020 · 4 comments
Open

Feature: Nested Matchers #81

fbartho opened this issue Jun 18, 2020 · 4 comments

Comments

@fbartho
Copy link
Contributor

fbartho commented Jun 18, 2020

The current Architecture doesn't really support nested matchers. It says if you match a thing, then the hunk of text that was matched is no longer eligible to be matched by something else.

And unfortunately this is order dependent so you should put your important patterns as early as possible in the pattern list.

Example:

[b]Bold[/b] [i]italic [b]italic and bold[/b][/i]

  1. If you have your matchers set to (Pseudocode) [ { …bold }, { …italic } ] then with this example, you'll get the bold stuff set right, but you won't get any italic code.

Bold [i]italic italic and bold [/i]

  1. If you have your matchers set to [ { …italic }, { …bold } ] then with this example text, you'll get the first bold section, and you'll get the rest in italics

Bold italic [b]italic and bold[/b]

Workaround

There could be a workaround for some, but not all use-cases, but this only works if you delete the tokens you're matching against -- so you can't keep the tokens in the original stream.

The workaround is recursive and a bit hard to describe:

[ { …boldStartPattern, tag: "boldStart" }, [ { …boldEndPattern, tag: "boldEnd", }, { …italicStartPattern, tag: "italicStart" }, { …italicEndPattern, tag: "italicEnd" } ]

Then you use TextExtraction directly, and recursively from within the "renderText" methods. Each unique renderText method deletes the Tag/Tokens they're matching against, and repeats the extraction recursively while mutating a global array of matched chunks / unmatched chunks.

Your result will be a deeply nested array of arrays, something like this:

[
  { children: "", tag: "boldStart" },
  { children: "Bold" },
  { children: "", tag: "boldEnd" },
  { children: " " },
  { children: "", tag: "italicStart" },
  { children: "italic" },
  [
    { children: "", tag: "boldStart" },
    { children: "italic and bold" },
    { children: "", tag: "boldEnd" },
  ],
  { children: "", tag: "italicEnd" },
]

While this isn't pretty, and wouldn't help for mismatched tags, it would probably be functional for rendering into a real UI.


Discussion

If people are interested in this feature, please mark your interests on this ticket or in comments, and let's discuss it.

@sellmeadog
Copy link

I have a pretty simple use case, but require nested matching nonetheless. After playing around with react-native-parsed-text, it was easier to roll my own component that meets my specific needs. The entire component and pattern matching logic is shown below and its simplicity matches that of my use case but perhaps it can be used as inspiration to provide more comprehensive nested pattern matching.

import React, { FunctionComponent, useState } from 'react';
import { StyleProp, Text, TextProps, TextStyle } from 'react-native';

interface P9TextParserConfig {
  pattern: RegExp;
  style?: StyleProp<TextStyle>;
  transform?(text: string): string;
}

interface P9TextParseResult {
  match: string;
  style?: StyleProp<TextStyle>;
}

export interface P9ParsedTextProps extends TextProps {
  parsers: P9TextParserConfig[];
}

export const P9ParsedText: FunctionComponent<P9ParsedTextProps> = ({
  children,
  parsers,
  style: rootStyle,
  ...rest
}) => {
  const [parsed] = useState(parseText([{ match: children as string }], parsers, 0, []));

  return (
    <>
      <Text {...rest} style={rootStyle}>
        {parsed.map(({ match, style }, index) =>
          style ? (
            <Text key={index.toString()} style={[rootStyle, style]}>
              {match}
            </Text>
          ) : (
            match
          )
        )}
      </Text>
    </>
  );
};

function parseText(
  input: P9TextParseResult[],
  parsers: P9TextParserConfig[],
  current: number,
  matches: P9TextParseResult[]
): P9TextParseResult[] {
  if (current === parsers.length) {
    return matches;
  }

  const { pattern, style, transform } = parsers[current];

  matches = input
    .map(({ match: parent, style: parentStyle }) =>
      parent
        .split(pattern)
        .map((match) =>
          pattern.test(match) ? { match: transform ? transform(match) : match, style } : { match, style: parentStyle }
        )
    )
    .reduce((output, next) => output.concat(next), []);

  return parseText(matches, parsers, ++current, matches);
}

@fbartho
Copy link
Contributor Author

fbartho commented Jul 30, 2020

@sellmeadog -- this is great! Were you proposing this as a way to rewrite this library? or just as an example on how to do it if people need this feature?

A. I'm just the steward of react-native-parsed-text, not the originator, so I'm probably not going to completely rewrite this library.
B. If it's just an example on how to do this if your application needs it, then I'd be comfortable closing this Feature request then!

@sellmeadog
Copy link

It's not a strict proposal, I was just trying to showcase what worked for me and see what value it might add to the overall discussion. I think it could be the base of a rewrite, but react-native-parsed-text seems to do a bit more like supporting click interaction, limits on matching, etc., which I have not taken into consideration.

@d11wtq
Copy link

d11wtq commented Nov 29, 2023

Can I propose a simple way to fix this would be to allow renderText to return a React Element? Then you could do like:

{
  pattern: /\w+-\d+/,
  renderText: (str, matches) => (
    <ParsedText
      parse={[ ... ]}
    >{str}</ParsedText>
  ),
}

This avoid complicating the parsing logic and moves the recursion onto the caller.

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

No branches or pull requests

3 participants