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

Trouble formatting bold/italic/underline text with capture group regex #33

Open
CaptainStiggz opened this issue May 25, 2018 · 3 comments

Comments

@CaptainStiggz
Copy link

CaptainStiggz commented May 25, 2018

I'm having trouble using reactStringReplace to format text with bold/italic tags. I have it set up as follows:

var text1 = "[b]Testing bold [i]and italic[/i] tags[/b]"
var text2 = "[b]Testing bold [i]and italic tags[/b][/i]"

let replaced = reactStringReplace(text1, /\[b\]([\s\S]+)\[\/b\]/g, (match, i) => {
   return <b key={i}>{match}</b>
})
replaced = reactStringReplace(replaced, /\[i\]([\s\S]+)\[\/i\]/g, (match, i) => {
   return <i key={i}>{match}</i>
})

// result (html)
<b>Testing [i]bold and italic[/i] tags</b>
<b>Testing [i]bold and italic tags</b>[/i]

I'm not sure if this is a problem with reactStringReplace, with the regex I'm using, or something else. If I apply the italic replace first, I get italic tags where I'd expect them to be, but the [b] tags remain unchanged. Is this use case possible using reactStringReplace or do I need to use dangerouslySetInnerHtml?

Bonus: is reactStringReplace capable of handling unbalanced tags, or improperly formatted tags as in text2 or should I be doing some pre-processing to ensure the strings are properly balanced and formatted?

@iansinnott
Copy link
Owner

Hey @CaptainStiggz, yeah this lib does not handle nested replacements. The reason being, it's meant to be roughly equivalent to String.prototype.replace in it's functionality, however, it is true that we run into limitations since replacements can change the data type to something other than strings.

If you look here you'll see that it only runs replacements on strings. Once your first replacement has been run the element in the result array will be an object (i.e. <b>...</b>) so no replacements will be run on the inner string.

I'm open to suggestions if you have thoughts about how we might support more robust replacements.


To your second point, it just depends on the regex. If you can craft a regex that handles unbalanced/malformed tags then it should work fine. But there's no internal logic here to augment the functionality of the regex.

@CaptainStiggz
Copy link
Author

CaptainStiggz commented May 26, 2018

Thanks @iansinnott! I've been fooling around with a recurisive augmentation as follows. It's still pretty rough, but it might be useful. This modification, combined with a slight modification to the regex I originally suggested (/\[i\]([\s\S]+)\[\/i\]/g should be lazy: /\[i\]([\s\S]+?)\[\/i\]/g) seems to work pretty well.

const reactStringReplaceRecursive = (input, pattern, fn, key=0) => {

   const isEmpty = (item) => {
      if(!item) return true
      if(item.hasOwnProperty('props')) {
         return false
      } else {
         return (item.length) ? false : true
      }
   }

   if(!input) {
      return null
   } else if(typeof input === "string") {
      return reactStringReplace(input, pattern, fn)
   }

   var output = []
   for(var i=0; i<input.length; i++) {
      const item = input[i]
      if(item) {
         if(typeof item === "string") {
            const next = reactStringReplace(item, pattern, fn)
            if(!isEmpty(next)) output.push(next)
         } else if(typeof item === "object") {        
            if(item.hasOwnProperty('props') && item.props.hasOwnProperty('children')) {
               const next = reactStringReplaceRecursive(item.props.children, pattern, fn, key+1)         
               if(!isEmpty(next)) {
                  const props = Object.assign({key: "k"+key+"i"+i}, item.props)
                  output.push(React.createElement(
                     item.type,
                     props,
                     next
                  ))
               }
            } else {
               const next = reactStringReplaceRecursive(item, pattern, fn, key+1)
               if(!isEmpty(next)) output.push(next)
            }
         }
      }
   }

   return output
}

This is a decent start, but still runs into some troublesome edge cases. For example, depending on the ordering of the bold/italic tags, I might end up with something like this:

const text = "[i][b]this should be bold and italic[/b][/i]"
// const text = "[b][i]this should be bold and italic[/i][/b]" - this would work
let replaced = reactStringReplace(text, /\[b\]([\s\S]+?)\[\/b\]/g, (match, i) => {
   return <b key={i}>{match}</b>
})
replaced = reactStringReplace(replaced, /\[i\]([\s\S]+?)\[\/i\]/g, (match, i) => {
   return <i key={i}>{match}</i>
})

// replaced => [
// "[i]",
// <b>this should be bold and italic</b>,
// "[/i]"
// ]

The problem gets pretty tricky here. Not exactly sure how I want to handle such cases.

@iansinnott
Copy link
Owner

You might consider using the React.Children API for the recursion in this case, since it will handle children regardless of whether it's a single element or an array. You would just need to check if the current item being iterated over was a react element.

That being said, you're use case is essentially a new rich text markup, which might be beyond the scope of simple regex and string replacement. You might consider using a stack to help you track opening/closing brackets and build up a deeply nested data structure representing your markup. Or do something like DraftJS does and annotate each character in the string with metadata which you subsequently use to render the markup. In either case you'll probably end up having to revert to strings and dangerouslySetInnerHTML.

DraftJS character metadata: https://draftjs.org/docs/api-reference-character-metadata.html

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