-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathXmlDiff.hs
180 lines (154 loc) · 5.87 KB
/
XmlDiff.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
{- |
Module : $Header$
Description : compute xml diffs
Copyright : (c) Christian Maeder, DFKI GmbH 2011
License : GPLv2 or higher, see LICENSE.txt
Maintainer : Christian.Maeder@dfki.de
Stability : provisional
Portability : portable
-}
module Common.XmlDiff where
import Common.ToXml
import Common.Utils as Utils
import Common.XPath
import Common.XUpdate
import Common.Lib.MapSet (setToMap)
import Data.List
import qualified Data.Set as Set
import qualified Data.Map as Map
import Text.XML.Light as XML
hetsTags :: UnordTags
hetsTags = Map.fromList
$ map (\ (e, as) -> (unqual e, Set.fromList $ map unqual as))
[ ("DGNode", ["name"])
, ("DGLink", ["linkid", "source", "target"])
, ("Axiom", [])
, ("Theorem", []) ]
{- for symbols the order matters. For axioms and theorems the names should be
stored separately -}
hetsXmlChanges :: Element -> Element -> [Change]
hetsXmlChanges e1 e2 = xmlDiff hetsTags [] Map.empty
[Elem $ cleanUpElem e1]
[Elem $ cleanUpElem e2]
hetsXmlDiff :: Element -> Element -> Element
hetsXmlDiff e = mkMods . hetsXmlChanges e
{- for elements, whose order does not matter, use the given attribute keys to
determine their equality. An empty set indicates an element that only contains
text to be compared. -}
type UnordTags = Map.Map QName (Set.Set QName)
-- keep track of the nth element with a given tag
type Count = Map.Map QName Int
{- we assume an element contains other elements and no text entries or just a
single text content -}
xmlDiff :: UnordTags -> [Step] -> Count -> [Content] -> [Content] -> [Change]
xmlDiff m stps em old new = case (old, filter validContent new) of
([], []) -> []
([], ns) ->
[Change (Add Append $ map contentToAddChange ns) $ pathToExpr stps]
(os, []) -> removeIns stps em os
(o : os, ns@(n : rt)) ->
if validContent o then
case o of
Elem e ->
let en = elName e
atts = elAttribs e
cs = elContent e
(nm, nstps) = extendPath en em stps
downDiffs = xmlElemDiff m nstps atts cs
restDiffs = xmlDiff m stps nm os
rmO = Change Remove (pathToExpr nstps) : restDiffs ns
in case Map.lookup en m of
Nothing -> case n of
Elem e2 | elName e2 == en ->
downDiffs e2
++ restDiffs rt
_ -> rmO
Just ats -> let
(mns, rns) = partition (matchElems en (strContent e) $
Map.intersection (attrMap atts) $ setToMap ats) ns
in case mns of
Elem mn : rm -> downDiffs mn
++ restDiffs (rm ++ rns)
_ -> rmO
XML.Text cd -> let inText = cdData cd in case n of
XML.Text cd2 | trim inText == trim nText
-> xmlDiff m stps em os rt
| otherwise -> Change (Update nText) (pathToExpr stps)
: xmlDiff m stps em os rt
where nText = cdData cd2
_ -> error "xmldiff2"
_ -> error "xmldiff"
else xmlDiff m stps em os ns
removeIns :: [Step] -> Count -> [Content] -> [Change]
removeIns stps em cs = case cs of
[] -> []
c : rs -> case c of
Elem e -> let
(nm, nstps) = extendPath (elName e) em stps
in Change Remove (pathToExpr nstps) : removeIns stps nm rs
_ -> Change (Update "") (pathToExpr stps) : removeIns stps em rs
-- does not work for multiple text entries
attrMap :: [Attr] -> Map.Map QName String
attrMap = Map.fromList . map (\ a -> (attrKey a, attrVal a))
matchElems :: QName -> String -> Map.Map QName String -> Content -> Bool
matchElems en t atts c = case c of
Elem e -> elName e == en
&& if Map.null atts then null (elChildren e) && strContent e == t else
Map.isSubmapOfBy (==) atts (attrMap $ elAttribs e)
_ -> False
xmlElemDiff :: UnordTags -> [Step] -> [Attr] -> [Content] -> Element -> [Change]
xmlElemDiff m nPath atts cs e2 = xmlAttrDiff nPath atts (elAttribs e2)
++ xmlDiff m nPath Map.empty cs (elContent e2)
xmlAttrDiff :: [Step] -> [Attr] -> [Attr] -> [Change]
xmlAttrDiff p a1 a2 = let
m1 = attrMap a1
m2 = attrMap a2
rms = Map.toList $ Map.difference m1 m2
ins = Map.toList $ Map.difference m2 m1
inter = Map.toList $ Map.filter (uncurry (/=))
$ Map.intersectionWith (,) m1 m2
addAttrStep a = pathToExpr $ Step Attribute (NameTest $ qName a) [] : p
in map (Change Remove . addAttrStep . fst) rms
++ map (\ (a, (_, v)) -> Change (Update v) $ addAttrStep a) inter
++ if null ins then [] else
[Change (Add Append $ map (AddAttr . uncurry Attr) ins) $ pathToExpr p]
pathToExpr :: [Step] -> Expr
pathToExpr = PathExpr Nothing . Path True . reverse
extendPath :: QName -> Count -> [Step] -> (Count, [Step])
extendPath en em stps = let
nm = Map.insertWith (+) en 1 em
i = Map.findWithDefault 1 en nm
nstps = Step Child (NameTest $ qName en) [PrimExpr Number $ show i] : stps
in (nm, nstps)
-- steps and predicates are reversed!
addPathNumber :: Int -> [Step] -> [Step]
addPathNumber i stps =
let e = PrimExpr Number $ show i
in case stps of
[] -> []
Step a n es : rs -> Step a n (e : es) : rs
contentToAddChange :: Content -> AddChange
contentToAddChange c = case c of
Elem e -> AddElem e
XML.Text t -> AddText $ cdData t
CRef s -> AddText s
mkXQName :: String -> QName
mkXQName s = (unqual s) { qPrefix = Just xupdateS }
changeToXml :: Change -> Element
changeToXml (Change csel pth) = let
sel = add_attr (mkAttr selectS $ show pth)
in case csel of
Add i as -> sel
. node (mkXQName $ showInsert i) $ map addsToXml as
Remove -> sel $ node (mkXQName removeS) ()
Update s -> sel $ node (mkXQName updateS) s
_ -> error "changeToXml"
addsToXml :: AddChange -> Content
addsToXml a = case a of
AddElem e -> Elem $ cleanUpElem e
AddAttr (Attr k v) -> Elem
. add_attr (mkNameAttr $ qName k) $ node (mkXQName attributeS) v
AddText s -> mkText s
_ -> error "addsToXml"
mkMods :: [Change] -> Element
mkMods = node (mkXQName "modifications") . map changeToXml