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

Styling Embedded <a> tags whilst making them clickable #346

Closed
russnettle opened this issue Aug 5, 2018 · 10 comments
Closed

Styling Embedded <a> tags whilst making them clickable #346

russnettle opened this issue Aug 5, 2018 · 10 comments

Comments

@russnettle
Copy link

russnettle commented Aug 5, 2018

Hey - I wonder if someone can help clarify if this is possible...

I am using HTML tags to style my text with a custom XMLStyler and this is working fine for something similar to:
<strong>Some text</strong><a href="https://www.website.com>link</a>"
however the html I am styling is coming through with links embedded within tags such as
<strong>Some text <a href="https://www.website.com>link</a></strong>"
I am able to add styles to the embedded link, however the link is not tappable.

My styler looks like :

class HTMLStyler: XMLStyler {
var body = StringStyle(
            .font(UIFont(titillium: weight, size: 14)),
            .color(.red),
            .lineSpacing(-3),
            .paragraphSpacingAfter(14)
        )
func style(forElement name: String, attributes: [String: String], currentStyle: StringStyle) -> StringStyle? {
 switch name {
 case "strong":
                let bolder = body.byAdding(
                    .xmlRules([
                        .style("em", body.byAdding(.font(UIFont(boldFont: .boldItalic, size: 14))))
                    ])
                )
                return bolder
case "a":
                guard let href = attributes["href"],
                    let url = URL(string: href) else {
                        return body
                }
                
                return body.byAdding(.link(url))
            default:
                return body
            }
 }

The problem I have is that the strong tag is picked up for the styling and returns the style before the 'a' tag, which means the .link(url) is not attached

Is there a way to get around this? maybe triggering a new styler somehow in the xml rules for the parent tag?

     case "strong":
                let bolder = body.byAdding(
                    .xmlRules([
                        .style("em", body.byAdding(.font(UIFont(boldFont: .boldItalic, size: 14)))),
//awesome method here to pass link attributes to new styler and return styled actionable link here?

                    ])
                )
                return bolder

I have been stuck on this for a while and the only way I can think to get around it if it doesn't work is to manually parse the string and close off the HTML tag prior to styling - which I would really like to avoid

Thanks so much - great framework that does everything else that I need

@russnettle
Copy link
Author

russnettle commented Aug 6, 2018

Just a quick update - I have tried the following

class HTMLStyler: XMLStyler {
var body = StringStyle(
            .font(UIFont(titillium: weight, size: 14)),
            .color(.red),
            .lineSpacing(-3),
            .paragraphSpacingAfter(14)
        )
  var bodyLink = StringStyle(
            .xmlStyler(LinkStyler())
        )
func style(forElement name: String, attributes: [String: String], currentStyle: StringStyle) -> StringStyle? {
 switch name {
 case "strong":
                let bolder = body.byAdding(
                    .xmlRules([
                        .style("em", body.byAdding(.font(UIFont(boldFont: .boldItalic, size: 14))))
.style("a", bodyLink)
                    ])
                )
                return bolder
case "a":
                guard let href = attributes["href"],
                    let url = URL(string: href) else {
                        return body
                }
                
                return body.byAdding(.link(url))
            default:
                return body
            }
 }

class LinkStyler: XMLStyler {
    
    func style(forElement name: String, attributes: [String: String], currentStyle: StringStyle) -> StringStyle? {
        
        switch name {
        case "a":
            guard let href = attributes["href"],
                let url = URL(string: href) else {
                    return currentStyle
            }
            
            return currentStyle.byAdding(.link(url))
        default:
            return currentStyle
        }
    }
    
    func prefix(forElement name: String, attributes: [String: String]) -> Composable? {
        return nil
    }
    
    func suffix(forElement name: String) -> Composable? {
        return nil
    }

Which feels more like what I was looking for, however style(forElement is never called

alternatively if was to manually call this within the xmlRules:

switch name {
 case "strong":
                let bolder = body.byAdding(
                    .xmlRules([
.style("a", LinkStyler().style(forElement: name, attributes: attributes, currentStyle: body)!)
                    ])
                )

The style(forElement method IS called, but its attributes are empty

@ZevEisenberg
Copy link
Collaborator

Great question, @russnettle. I'd love to make this easier to handle in BonMot, possibly without needing a custom styler. I'm not sure when I'll have a chance to dig into your question more, but I definitely want to improve this. Hopefully I can get back to you soon on this.

@russnettle
Copy link
Author

Thanks @ZevEisenberg appreciate it - I am currently looking to have to write reg ex to parse the html and close / reopen any styling tags around link before it hits the styler. I wondered if I was just missing something in BonMot that would get around this ?

@ZevEisenberg
Copy link
Collaborator

This should be possible, but we may need to improve the custom styler API so that you can combine attributes as you encounter nested styles. I'm not sure if we support that correctly, but I won't know for sure without looking into it. I'd also love to support this natively, so you don't need to write a custom styler to support it.

If this is something you're interested in contributing to, and you have the free time, a great starting point would be to implement a failing test case that demonstrates the behavior you desire. This could be either a demonstration of a custom styler that doesn't combine nested attributes correctly, or a hypothetical update to BonMot itself that supports parsing HTML href tags. I'm not sure which of those I'd prefer, but would love to discuss pros and cons.

@Imperiopolis
Copy link
Collaborator

Imperiopolis commented Aug 6, 2018

@russnettle I think this should work if in your very first example instead of doing body.byAdding (basically resetting to base style, as you define this externally) you do currentStyle.byAdding. This should allow styles to nest properly. If you need to also append body, then you'll probably want to append that to currentStyle as well.

@Imperiopolis
Copy link
Collaborator

Imperiopolis commented Aug 6, 2018

In addition, I noticed you're returning XMLRules from your XMLStyler – if you're overriding style(forElement:) you don't want to use the .xmlRules tag since the XML is already parsed, in this case you should just return the style you want to append for the given tag directly.

I believe if you also just return a new string style, it will automatically append to current style. For example, this code works for me:

    func style(forElement name: String, attributes: [String: String], currentStyle: StringStyle) -> StringStyle? {
        switch name {
        case "i", "em":
            let f = currentStyle.font ?? TextStyles.defaultFont

            guard f.fontName != BuenosAires.bold.rawValue else {
                return StringStyle(.font(BuenosAires.boldItalic, f.pointSize))
            }

            return [.font(BuenosAires.italic, f.pointSize)]
        case "b", "strong":
            let f = currentStyle.font ?? TextStyles.defaultFont

            guard f.fontName != BuenosAires.italic.rawValue else {
                return StringStyle(.font(BuenosAires.boldItalic, f.pointSize))
            }

            return StringStyle(.font(BuenosAires.bold, f.pointSize))
        case "a":
            guard let href = attributes["href"], let url = URL(string: href, relativeTo: Server.current.url) else { return nil }
            return StringStyle(.link(url))
        default:
            return nil
        }
    }

Or to use your example, I believe this should also work.

class HTMLStyler: XMLStyler {
var body = StringStyle(
            .font(UIFont(titillium: weight, size: 14)),
            .color(.red),
            .lineSpacing(-3),
            .paragraphSpacingAfter(14)
        )
func style(forElement name: String, attributes: [String: String], currentStyle: StringStyle) -> StringStyle? {
 switch name {
 case "strong":
                return StringStyle(.font(UIFont(boldFont: .boldItalic, size: 14)))
 case "a":
                guard let href = attributes["href"],
                    let url = URL(string: href) else {
                        return nil
                }
                
                return StringStyle(.link(url))
            default:
                return body
            }
 }

@russnettle
Copy link
Author

@Imperiopolis hey - thanks for clarifying - I will take a look - however from what I have already seen , if an a tag is embedded within a strong tag, the a is never reached as the strong returns early missing the nested tag. The only way I could achieve styling is via the XML rules . Links are working fine as are strong and other tags that are nested - links are the only issue I have - will take a look further though

@Imperiopolis
Copy link
Collaborator

Imperiopolis commented Aug 6, 2018

@russnettle link tags do work nested for me. I think the key thing for you to try and have them work for you also would be to not return .xmlRules from your strong case and instead just return the actual string style you want to apply for strong.

See my above example for exactly what I mean.

@russnettle
Copy link
Author

Thanks @Imperiopolis I will give this a try 😃

@russnettle
Copy link
Author

Hey @Imperiopolis removing the .xmlRules and doing a manual comparison for fonts (as in you're examples worked) thanks so much :)

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

3 participants