{-|
Module: IHP.ServerSideComponent.HtmlDiff
Copyright: (c) digitally induced GmbH, 2021
Description: Provides differences and patchsets between two HTML fragments
-}
module IHP.ServerSideComponent.HtmlDiff where

import IHP.Prelude
import IHP.ServerSideComponent.HtmlParser
import qualified Data.Text as Text
import Text.Megaparsec.Error (ParseErrorBundle)
import Data.Void (Void)

data NodeOperation
    = UpdateTextContent { NodeOperation -> Text
textContent :: !Text, NodeOperation -> [Int]
path :: ![Int] }
    | ReplaceNode { NodeOperation -> Node
oldNode :: !Node, NodeOperation -> Node
newNode :: !Node, NodeOperation -> Text
newNodeHtml :: !Text, path :: ![Int] }
    | UpdateNode { NodeOperation -> [AttributeOperation]
attributeOperations :: ![AttributeOperation], path :: ![Int] }
    | UpdateComment { NodeOperation -> Text
comment :: !Text, path :: ![Int] }
    | UpdatePreEscapedTextNode { textContent :: !Text, path :: ![Int] }
    | DeleteNode { NodeOperation -> Node
node :: !Node, path :: ![Int] }
    | CreateNode { NodeOperation -> Text
html :: !Text, path :: ![Int] }
    deriving (NodeOperation -> NodeOperation -> Bool
(NodeOperation -> NodeOperation -> Bool)
-> (NodeOperation -> NodeOperation -> Bool) -> Eq NodeOperation
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: NodeOperation -> NodeOperation -> Bool
== :: NodeOperation -> NodeOperation -> Bool
$c/= :: NodeOperation -> NodeOperation -> Bool
/= :: NodeOperation -> NodeOperation -> Bool
Eq, Int -> NodeOperation -> ShowS
[NodeOperation] -> ShowS
NodeOperation -> String
(Int -> NodeOperation -> ShowS)
-> (NodeOperation -> String)
-> ([NodeOperation] -> ShowS)
-> Show NodeOperation
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> NodeOperation -> ShowS
showsPrec :: Int -> NodeOperation -> ShowS
$cshow :: NodeOperation -> String
show :: NodeOperation -> String
$cshowList :: [NodeOperation] -> ShowS
showList :: [NodeOperation] -> ShowS
Show)

data AttributeOperation
    = UpdateAttribute { AttributeOperation -> Text
attributeName :: !Text, AttributeOperation -> Text
attributeValue :: !Text }
    | AddAttribute { attributeName :: !Text, attributeValue :: !Text }
    | DeleteAttribute { attributeName :: !Text }
    deriving (AttributeOperation -> AttributeOperation -> Bool
(AttributeOperation -> AttributeOperation -> Bool)
-> (AttributeOperation -> AttributeOperation -> Bool)
-> Eq AttributeOperation
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AttributeOperation -> AttributeOperation -> Bool
== :: AttributeOperation -> AttributeOperation -> Bool
$c/= :: AttributeOperation -> AttributeOperation -> Bool
/= :: AttributeOperation -> AttributeOperation -> Bool
Eq, Int -> AttributeOperation -> ShowS
[AttributeOperation] -> ShowS
AttributeOperation -> String
(Int -> AttributeOperation -> ShowS)
-> (AttributeOperation -> String)
-> ([AttributeOperation] -> ShowS)
-> Show AttributeOperation
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AttributeOperation -> ShowS
showsPrec :: Int -> AttributeOperation -> ShowS
$cshow :: AttributeOperation -> String
show :: AttributeOperation -> String
$cshowList :: [AttributeOperation] -> ShowS
showList :: [AttributeOperation] -> ShowS
Show)

diffHtml :: Text -> Text -> Either (ParseErrorBundle Text Void) [NodeOperation]
diffHtml :: Text -> Text -> Either (ParseErrorBundle Text Void) [NodeOperation]
diffHtml Text
a Text
b = do
    Node
nodeA <- Text -> Either (ParseErrorBundle Text Void) Node
parseHtml Text
a
    Node
nodeB <- Text -> Either (ParseErrorBundle Text Void) Node
parseHtml Text
b

    let ?oldHtml = ?oldHtml::Text
Text
a
    let ?newHtml = ?newHtml::Text
Text
b

    [NodeOperation]
-> Either (ParseErrorBundle Text Void) [NodeOperation]
forall a. a -> Either (ParseErrorBundle Text Void) a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Node -> Node -> [NodeOperation]
forall text.
(?oldHtml::text, ?newHtml::Text) =>
Node -> Node -> [NodeOperation]
diffNodes Node
nodeA Node
nodeB)

type Path = [Int]

diffNodes :: (?oldHtml :: text, ?newHtml :: Text) => Node -> Node -> [NodeOperation]
diffNodes :: forall text.
(?oldHtml::text, ?newHtml::Text) =>
Node -> Node -> [NodeOperation]
diffNodes = [Int] -> Node -> Node -> [NodeOperation]
forall text.
(?oldHtml::text, ?newHtml::Text) =>
[Int] -> Node -> Node -> [NodeOperation]
diffNodes' []

diffNodes' :: (?oldHtml :: text, ?newHtml :: Text) => Path -> Node -> Node -> [NodeOperation]
diffNodes' :: forall text.
(?oldHtml::text, ?newHtml::Text) =>
[Int] -> Node -> Node -> [NodeOperation]
diffNodes' [Int]
path TextNode { $sel:textContent:Node :: Node -> Text
textContent = Text
oldTextContent } TextNode { $sel:textContent:Node :: Node -> Text
textContent = Text
newTextContent } =
        if Text
oldTextContent Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
newTextContent
            then []
            else [UpdateTextContent { $sel:textContent:UpdateTextContent :: Text
textContent = Text
newTextContent, [Int]
$sel:path:UpdateTextContent :: [Int]
path :: [Int]
path }]
diffNodes' [Int]
path CommentNode { $sel:comment:Node :: Node -> Text
comment = Text
oldComment } CommentNode { $sel:comment:Node :: Node -> Text
comment = Text
newComment } =
        if Text
oldComment Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
newComment
            then []
            else [UpdateComment { $sel:comment:UpdateTextContent :: Text
comment = Text
newComment, [Int]
$sel:path:UpdateTextContent :: [Int]
path :: [Int]
path }]
diffNodes' [Int]
path Node
NoRenderCommentNode Node
NoRenderCommentNode = []
diffNodes' [Int]
path PreEscapedTextNode { $sel:textContent:Node :: Node -> Text
textContent = Text
oldTextContent } PreEscapedTextNode { $sel:textContent:Node :: Node -> Text
textContent = Text
newTextContent } =
        if Text
oldTextContent Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
newTextContent
            then []
            else [UpdatePreEscapedTextNode { $sel:textContent:UpdateTextContent :: Text
textContent = Text
newTextContent, [Int]
$sel:path:UpdateTextContent :: [Int]
path :: [Int]
path }]
diffNodes' [Int]
path oldNode :: Node
oldNode@(Node { $sel:tagName:Node :: Node -> Text
tagName = Text
oldTagName, $sel:attributes:Node :: Node -> [Attribute]
attributes = [Attribute]
oldAttributes, $sel:children:Node :: Node -> [Node]
children = [Node]
oldChildren }) newNode :: Node
newNode@(Node { $sel:tagName:Node :: Node -> Text
tagName = Text
newTagName, $sel:attributes:Node :: Node -> [Attribute]
attributes = [Attribute]
newAttributes, $sel:children:Node :: Node -> [Node]
children = [Node]
newChildren }) =
    if Text
oldTagName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
newTagName
        then
            let
                attributeOperations :: [AttributeOperation]
attributeOperations = [Attribute] -> [Attribute] -> [AttributeOperation]
diffAttributes [Attribute]
oldAttributes [Attribute]
newAttributes
                childrenNodeOperations :: [NodeOperation]
childrenNodeOperations = [Int] -> Node -> Node -> [NodeOperation]
forall text.
(?oldHtml::text, ?newHtml::Text) =>
[Int] -> Node -> Node -> [NodeOperation]
diffNodes' [Int]
path Children { $sel:children:Node :: [Node]
children = [Node]
oldChildren } Children { $sel:children:Node :: [Node]
children = [Node]
newChildren }
            in [[NodeOperation]] -> [NodeOperation]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [
                    if [AttributeOperation] -> Bool
forall value. IsEmpty value => value -> Bool
isEmpty [AttributeOperation]
attributeOperations
                        then []
                        else [UpdateNode { [AttributeOperation]
$sel:attributeOperations:UpdateTextContent :: [AttributeOperation]
attributeOperations :: [AttributeOperation]
attributeOperations, [Int]
$sel:path:UpdateTextContent :: [Int]
path :: [Int]
path }]
                    , [NodeOperation]
childrenNodeOperations
                    ]

        else [ReplaceNode { Node
$sel:oldNode:UpdateTextContent :: Node
oldNode :: Node
oldNode, Node
$sel:newNode:UpdateTextContent :: Node
newNode :: Node
newNode, $sel:newNodeHtml:UpdateTextContent :: Text
newNodeHtml = Node -> Text -> Text
nodeOuterHtml Node
newNode ?newHtml::Text
Text
?newHtml, [Int]
$sel:path:UpdateTextContent :: [Int]
path :: [Int]
path }]
diffNodes' [Int]
path Children { $sel:children:Node :: Node -> [Node]
children = [Node]
oldChildren } Children { $sel:children:Node :: Node -> [Node]
children = [Node]
newChildren } =
        let
            patchElements :: [Node] -> [Node] -> Int -> [NodeOperation]
            patchElements :: [Node] -> [Node] -> Int -> [NodeOperation]
patchElements (Node
new:Node
nextNewNode:[Node]
newRest) (Node
old:[Node]
oldRest) !Int
index | (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Node
new Node -> Node -> Bool
`isNodeEqIgnoringPosition` Node
old) Bool -> Bool -> Bool
&& (Node
old Node -> Node -> Bool
`isNodeEqIgnoringPosition` Node
nextNewNode) = [ CreateNode { $sel:html:UpdateTextContent :: Text
html = Node -> Text -> Text
nodeOuterHtml Node
new ?newHtml::Text
Text
?newHtml, $sel:path:UpdateTextContent :: [Int]
path = (Int
indexInt -> [Int] -> [Int]
forall a. a -> [a] -> [a]
:[Int]
path) } ] [NodeOperation] -> [NodeOperation] -> [NodeOperation]
forall a. Semigroup a => a -> a -> a
<> ([Node] -> [Node] -> Int -> [NodeOperation]
patchElements ([Node]
newRest) ([Node]
oldRest) (Int
index Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2)) -- [A, C <old>] -> [A, B <new>, C <nextNewNode>]
            patchElements (Node
new:[Node]
newRest) (Node
old:Node
nextOld:[Node]
oldRest) !Int
index | (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Node
new Node -> Node -> Bool
`isNodeEqIgnoringPosition` Node
old) Bool -> Bool -> Bool
&& (Node
new Node -> Node -> Bool
`isNodeEqIgnoringPosition` Node
nextOld) = [ DeleteNode { $sel:node:UpdateTextContent :: Node
node = Node
old, $sel:path:UpdateTextContent :: [Int]
path = (Int
indexInt -> [Int] -> [Int]
forall a. a -> [a] -> [a]
:[Int]
path) } ] [NodeOperation] -> [NodeOperation] -> [NodeOperation]
forall a. Semigroup a => a -> a -> a
<> ([Node] -> [Node] -> Int -> [NodeOperation]
patchElements ([Node]
newRest) ([Node]
oldRest) (Int
index Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)) -- [A, B <old>, C <nextOldNode> ] -> [A, C <new>]
            patchElements (Node
new:[Node]
newRest) (Node
old:[Node]
oldRest) !Int
index = ([Int] -> Node -> Node -> [NodeOperation]
forall text.
(?oldHtml::text, ?newHtml::Text) =>
[Int] -> Node -> Node -> [NodeOperation]
diffNodes' (Int
indexInt -> [Int] -> [Int]
forall a. a -> [a] -> [a]
:[Int]
path) Node
old Node
new) [NodeOperation] -> [NodeOperation] -> [NodeOperation]
forall a. Semigroup a => a -> a -> a
<> ([Node] -> [Node] -> Int -> [NodeOperation]
patchElements [Node]
newRest [Node]
oldRest (Int
index Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1))
            patchElements (Node
new:[Node]
newRest) [] !Int
index = [ CreateNode { $sel:html:UpdateTextContent :: Text
html = Node -> Text -> Text
nodeOuterHtml Node
new ?newHtml::Text
Text
?newHtml, $sel:path:UpdateTextContent :: [Int]
path = (Int
indexInt -> [Int] -> [Int]
forall a. a -> [a] -> [a]
:[Int]
path) } ] [NodeOperation] -> [NodeOperation] -> [NodeOperation]
forall a. Semigroup a => a -> a -> a
<> ([Node] -> [Node] -> Int -> [NodeOperation]
patchElements [Node]
newRest [] (Int
index Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1))
            patchElements [] (Node
old:[Node]
oldRest) !Int
index = [ DeleteNode { $sel:node:UpdateTextContent :: Node
node = Node
old, $sel:path:UpdateTextContent :: [Int]
path = (Int
indexInt -> [Int] -> [Int]
forall a. a -> [a] -> [a]
:[Int]
path) } ] [NodeOperation] -> [NodeOperation] -> [NodeOperation]
forall a. Semigroup a => a -> a -> a
<> ([Node] -> [Node] -> Int -> [NodeOperation]
patchElements [] [Node]
oldRest (Int
index Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1))
            patchElements [] [] Int
_ = []
        in
            [Node] -> [Node] -> Int -> [NodeOperation]
patchElements [Node]
newChildren [Node]
oldChildren Int
0
diffNodes' [Int]
path Node
oldNode Node
newNode = [ReplaceNode { Node
$sel:oldNode:UpdateTextContent :: Node
oldNode :: Node
oldNode, Node
$sel:newNode:UpdateTextContent :: Node
newNode :: Node
newNode, $sel:newNodeHtml:UpdateTextContent :: Text
newNodeHtml = Node -> Text -> Text
nodeOuterHtml Node
newNode ?newHtml::Text
Text
?newHtml, [Int]
$sel:path:UpdateTextContent :: [Int]
path :: [Int]
path }]


diffAttributes :: [Attribute] -> [Attribute] -> [AttributeOperation]
diffAttributes :: [Attribute] -> [Attribute] -> [AttributeOperation]
diffAttributes [Attribute]
old [Attribute]
new = [AttributeOperation]
addOrUpdateAttributes [AttributeOperation]
-> [AttributeOperation] -> [AttributeOperation]
forall a. Semigroup a => a -> a -> a
<> [AttributeOperation]
deleteAttributes
    where
        addOrUpdateAttributes :: [AttributeOperation]
        addOrUpdateAttributes :: [AttributeOperation]
addOrUpdateAttributes =
                [Attribute]
new
                [Attribute]
-> ([Attribute] -> [Maybe Attribute]) -> [Maybe Attribute]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Attribute -> Maybe Attribute) -> [Attribute] -> [Maybe Attribute]
forall a b. (a -> b) -> [a] -> [b]
map ([Attribute] -> Attribute -> Maybe Attribute
matchAttribute [Attribute]
old)
                [Maybe Attribute]
-> ([Maybe Attribute] -> [(Attribute, Maybe Attribute)])
-> [(Attribute, Maybe Attribute)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Attribute] -> [Maybe Attribute] -> [(Attribute, Maybe Attribute)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Attribute]
new
                [(Attribute, Maybe Attribute)]
-> ([(Attribute, Maybe Attribute)] -> [AttributeOperation])
-> [AttributeOperation]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Attribute, Maybe Attribute) -> Maybe AttributeOperation)
-> [(Attribute, Maybe Attribute)] -> [AttributeOperation]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (Attribute, Maybe Attribute) -> Maybe AttributeOperation
diffMatchedAttribute

        deleteAttributes :: [AttributeOperation]
        deleteAttributes :: [AttributeOperation]
deleteAttributes =
                [Attribute]
old
                [Attribute]
-> ([Attribute] -> [Maybe Attribute]) -> [Maybe Attribute]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Attribute -> Maybe Attribute) -> [Attribute] -> [Maybe Attribute]
forall a b. (a -> b) -> [a] -> [b]
map ([Attribute] -> Attribute -> Maybe Attribute
matchAttribute [Attribute]
new)
                [Maybe Attribute]
-> ([Maybe Attribute] -> [(Attribute, Maybe Attribute)])
-> [(Attribute, Maybe Attribute)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Attribute] -> [Maybe Attribute] -> [(Attribute, Maybe Attribute)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Attribute]
old
                [(Attribute, Maybe Attribute)]
-> ([(Attribute, Maybe Attribute)]
    -> [(Attribute, Maybe Attribute)])
-> [(Attribute, Maybe Attribute)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Attribute, Maybe Attribute) -> Bool)
-> [(Attribute, Maybe Attribute)] -> [(Attribute, Maybe Attribute)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(Attribute
_, Maybe Attribute
newAttribute) -> Maybe Attribute -> Bool
forall a. Maybe a -> Bool
isNothing Maybe Attribute
newAttribute)
                [(Attribute, Maybe Attribute)]
-> ([(Attribute, Maybe Attribute)] -> [AttributeOperation])
-> [AttributeOperation]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Attribute, Maybe Attribute) -> AttributeOperation)
-> [(Attribute, Maybe Attribute)] -> [AttributeOperation]
forall a b. (a -> b) -> [a] -> [b]
map (\(Attribute { Text
attributeName :: Text
$sel:attributeName:Attribute :: Attribute -> Text
attributeName }, Maybe Attribute
_) -> DeleteAttribute { Text
$sel:attributeName:UpdateAttribute :: Text
attributeName :: Text
attributeName })

        diffMatchedAttribute :: (Attribute, Maybe Attribute) -> Maybe AttributeOperation
        diffMatchedAttribute :: (Attribute, Maybe Attribute) -> Maybe AttributeOperation
diffMatchedAttribute (Attribute { Text
$sel:attributeName:Attribute :: Attribute -> Text
attributeName :: Text
attributeName, $sel:attributeValue:Attribute :: Attribute -> Text
attributeValue = Text
newValue }, Just Attribute { $sel:attributeValue:Attribute :: Attribute -> Text
attributeValue = Text
oldValue }) | Text
newValue Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
oldValue = Maybe AttributeOperation
forall a. Maybe a
Nothing
        diffMatchedAttribute (Attribute { Text
$sel:attributeName:Attribute :: Attribute -> Text
attributeName :: Text
attributeName, $sel:attributeValue:Attribute :: Attribute -> Text
attributeValue = Text
newValue }, Just Attribute { $sel:attributeValue:Attribute :: Attribute -> Text
attributeValue = Text
oldValue }) = AttributeOperation -> Maybe AttributeOperation
forall a. a -> Maybe a
Just UpdateAttribute { Text
$sel:attributeName:UpdateAttribute :: Text
attributeName :: Text
attributeName, $sel:attributeValue:UpdateAttribute :: Text
attributeValue = Text
newValue }
        diffMatchedAttribute (Attribute { Text
$sel:attributeName:Attribute :: Attribute -> Text
attributeName :: Text
attributeName, Text
$sel:attributeValue:Attribute :: Attribute -> Text
attributeValue :: Text
attributeValue }, Maybe Attribute
Nothing) = AttributeOperation -> Maybe AttributeOperation
forall a. a -> Maybe a
Just AddAttribute { Text
$sel:attributeName:UpdateAttribute :: Text
attributeName :: Text
attributeName, Text
$sel:attributeValue:UpdateAttribute :: Text
attributeValue :: Text
attributeValue }

        -- | Finds an attribute in 'old' with the same attribute name
        matchAttribute :: [Attribute] -> Attribute -> Maybe Attribute
        matchAttribute :: [Attribute] -> Attribute -> Maybe Attribute
matchAttribute [Attribute]
attributes Attribute { Text
$sel:attributeName:Attribute :: Attribute -> Text
attributeName :: Text
attributeName } = (Attribute -> Bool) -> [Attribute] -> Maybe Attribute
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\Attribute { $sel:attributeName:Attribute :: Attribute -> Text
attributeName = Text
attributeName' } -> Text
attributeName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
attributeName' ) [Attribute]
attributes

-- | Grabs the entire HTML string corresponding to the node boundaries.
--
-- Node boundaries are only stored for 'Node'. Other nodes ('TextNode', etc) don't store start/end offset, so we render
-- them by ourselves.
nodeOuterHtml :: Node -> Text -> Text
nodeOuterHtml :: Node -> Text -> Text
nodeOuterHtml Node { Int
startOffset :: Int
$sel:startOffset:Node :: Node -> Int
startOffset, Int
endOffset :: Int
$sel:endOffset:Node :: Node -> Int
endOffset } Text
html = Text
html
        Text -> (Text -> Text) -> Text
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Int -> Text -> Text
Text.drop Int
startOffset
        Text -> (Text -> Text) -> Text
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Int -> Text -> Text
Text.take (Int
endOffset Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
startOffset)
-- Assuming chars are already escaped, because that's what HSX produces
nodeOuterHtml TextNode { Text
$sel:textContent:Node :: Node -> Text
textContent :: Text
textContent } Text
_ = Text
textContent
nodeOuterHtml PreEscapedTextNode { Text
$sel:textContent:Node :: Node -> Text
textContent :: Text
textContent } Text
_ = Text
textContent
nodeOuterHtml Children { [Node]
$sel:children:Node :: Node -> [Node]
children :: [Node]
children } Text
html = [Text] -> Text
forall a. Monoid a => [a] -> a
mconcat ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ (Node -> Text) -> [Node] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Node -> Text -> Text
`nodeOuterHtml` Text
html) [Node]
children
nodeOuterHtml CommentNode { Text
$sel:comment:Node :: Node -> Text
comment :: Text
comment } Text
_ = Text
"<!--" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
comment Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"-->"

isNodeEqIgnoringPosition :: Node -> Node -> Bool
isNodeEqIgnoringPosition :: Node -> Node -> Bool
isNodeEqIgnoringPosition a :: Node
a@(Node {}) b :: Node
b@(Node {}) = (Node
a { $sel:startOffset:Node :: Int
startOffset = Int
0, $sel:endOffset:Node :: Int
endOffset = Int
0 }) Node -> Node -> Bool
forall a. Eq a => a -> a -> Bool
== (Node
b { $sel:startOffset:Node :: Int
startOffset = Int
0, $sel:endOffset:Node :: Int
endOffset = Int
0 })
isNodeEqIgnoringPosition Node
a Node
b = Node
a Node -> Node -> Bool
forall a. Eq a => a -> a -> Bool
== Node
b