Open drakes00 opened 1 year ago
Hi @drakes00,
first, thank you for your interest in using and possibly enhancing this project.
Unfortunately, I am not sure to have understood what you want to do and where the problem lies. Please explain a bit further what you want to do and where the limitation / challenge of the library lies.
As you might already know, comparison of mindmap contents is not too easy to do. Especially, when nodes are so rich as with Freeplane. You ask for "needed" attributes when comparing mindmaps and their nodes, Here are a few:
So, first, you would have to define for yourself what you mean by "equality". If equality of node's plaintext
values is sufficient for you, it's fine and in no way complicated. But as soon as e.g. you are working together with other people on a mindmap, either a strict ruleset is to be defined or all the possible changes have to be considered. And, yes, freeplane-python-io
would be a good tool to help with this.
Within the Freeplane community, the subject of "comparison of mindmaps" was started within one of the discussions. It was tried to use applications like Notepad++ to do a XML text comparison which results in high complexity, as it is not easily possible to separate important from irrelevant information just by looking at the XML source of the mindmap.
One solution for comparing mindmaps could be to "convert" each mindmap into a "minimal" mindmap (only those node attributes are contained that are relevant for a comparison, so e.g. "folding state" would not be considered), and then do a textual comparison using e.g. Notepad++ or other tools which result in a line-based and colored comparison output. Where e.g. GREEN lines are new
on either side, RED lines have been deleted
from the other side, and YELLOW lines contain changes
on both sides. This could be done using this library in combination with a suitable Python GUI library.
Anyway. Again, thanks for your interest.
Looking forward to reading from you again.
Hi @nnako,
Thanks a lot for this detailed answer. Please excuse the shortness of my initial question, I didn't thought it was a complicated issue :smiley:
To clarify my initial goal, I am relying on freeplane-python-io
to parse mindmaps because I'm not ultra satisfied with the Markdown export feature of freeplane (with the increasing # characters).
I am thus writing a piece of python code translating mindmaps into markdown the way I want it and I am writing testcases for this code.
While wanting to test my code, I needed to compare what I am generating against what I consider as a good conversion in my case, leading to comparing mindmap objects.
I only considered the plaintext
field to have a very simple working Mindmap.__eq__
method but I agree with you that only considering plaintext
is way to loose of an equality property leading for my initial question on what should equality look like. This is what I have for the moment:
def node__eq__(self, __value: freeplane.Node) -> bool:
if self.plaintext != __value.plaintext:
return False
if len(self.children) != len(__value.children):
return False
return all([self.children[i] == __value.children[i] for i in range(len(self.children))])
def mm__eq__(self, __value: freeplane.Mindmap) -> bool:
if self.rootnode != __value.rootnode:
return False
return True
The test on Node.plaintext
in node__eq__
cas easily be complexified to take over attributes into account, which lead to my initial question. In my opinion, equality should consider all attributes that do not change on a save without modification, thus excluding attributes such as creation_date
or modification_date
. The fold_status
is a good question, though. I feel like it doesn't change the map itself but rather its appearance.
Given the list you proposed earlier, I guess comparison could be done on:
Do you have preferences?
Regards,
Hi @drakes00 . Great idea. That is exactly why I initially intended when creating freeplane-python-io
: to be able to interface out directly from the freeplane file. Without being dependent on the features of the Freeplane editor itself.
About comparing mindmaps, as I have understood your goal so far, you might be interested in the following:
attributes
and style
only make sense for inclusion into equality comparison when they are based on a fix set of possible values.I am still not sure that I have fully understood where exactly the use of freeplane-python-io
supports your development. Is it just a part of the "testing"? In my opinion, I could make sense to use freeplane-python-io
to create the whole exporting application. Like I would imagine other applications like
In every case, freeplane-python-io
would be the interfacing layer between the two applications, directly. Prerequisit would always be that the structure of the source Freeplane mindmap is very strict or at least as flexible as you would expect people to intuitively work with it. Otherwise the conversion breaks when confronted with special cases.
But, even if I don't unserstand it fully, just go for it and we will see.
Thanks for your interest.
- you might rather consider comparing trees not just single nodes. as I see it, currently, you only consider the 1st level children of a node.
It is actually the case, the line return all([self.children[i] == __value.children[i] for i in range(len(self.children))])
is comparing children which nodes thus recursively calling node__eq__
for each child (which will also recurse on its children as well.
I had this function overriding the __eq__
method with: freeplane.Node.__eq__ = node__eq__
(which would not be required if included natively in freeplane-python-io
.
- yes, your list of node properties seem to be reasonable for comparison.
attributes
andstyle
only make sense for inclusion into equality comparison when they are based on a fix set of possible values.
OK cool, will PR soon with those and we can work from there.
- to render your feature as flexible as possible (others might use it to export a slightly different markdown file), you would have to provide some parameters with your functions to setup provided operations.
This is actually not easy since the __eq__
method is standardize and does not take parmeters to the best of my knowledge?
However, we could imagine providing an __eq__
method that does a simple comparison and a Mindmap.deepComparison()
method that users can explicitly call for advanced comparison features?
I am still not sure that I have fully understood where exactly the use of
freeplane-python-io
supports your development. Is it just a part of the "testing"? In my opinion, I could make sense to usefreeplane-python-io
to create the whole exporting application. Like I would imagine other applications like
- Freeplane -> Todolist
- Freeplane -> Excel
- Freeplane -> PowerPoint
- Freeplane -> Confluence
- ...
I am actually relying on feeplane-python-io
to develop this export to markdown feature, and also to test this feature. We could actually consider including such conversions to various formats (mine is actually for Todolists). But I can probably work on it for a while and coming back to you with this export to Todolist feature if you would like it merged?
In every case,
freeplane-python-io
would be the interfacing layer between the two applications, directly. Prerequisit would always be that the structure of the source Freeplane mindmap is very strict or at least as flexible as you would expect people to intuitively work with it. Otherwise the conversion breaks when confronted with special cases.
Yes indeed.
Thanks for you opinion anyway, Regards,
Hi all, I use a few scripts to compare modifications/differences in mindmaps I use to modify them every time I use them, depending what I want to compare to define if two nodes are equql or not.
The first one is maybe easier to understand, but I don't use it very much
If both branches are in the same map (sometimes I copy just the two branches I want to compare in a new map), I use this script:
tests = [
{a,b -> a.text == b.text}
//,{a,b -> a.id == b.id}
,{a,b -> a.details?.htmlText == b.details?.htmlText}
,{a,b -> a.note?.html == b.note?.html}
,{a,b -> a.children.size() == b.children.size()}
,{a,b -> a.children*.text == b.children*.text}
,{a,b -> a.attributes == a.attributes}
,{a,b -> a.style.name == a.style.name}
,{a,b -> a.icons.icons == a.icons.icons}
]
def (a,b) = c.selecteds.take(2)
println a.text
println b.text
println equals(a,b)
comparar(a,b)
def comparar(x,y){
println x.text
if(equals(x,y)){
def xChilds = x.children
def yChilds = y.children
for(def i=0 ; i< xChilds.size(); i++){
def resp = comparar(xChilds[i],yChilds[i])
if(!resp){
return false
}
}
return true
}else{
equals(x,y, true)
x.pathToRoot.dropRight(1).reverse()*.folded = false
y.pathToRoot.dropRight(1).reverse()*.folded = false
c.select([x,y])
return false
}
}
def equals(x,y,doPrint = false){
def test = true
def i = 0
def iMax = tests.size()
while(test && i<iMax){
test = tests[i](x,y)
if(doPrint){
println i + ' ' + tests[i].toString() + ' ' + test
}
i++
}
println i
return test
}
It compares all the nodes, one by one. Both maps must be open and their names must start with the same string
def mapNameIni = 'Scripts' //here you must change the text with the initial text of the mindmaps names
def mapsFilter = {mapa -> mapa.name.startsWith(mapNameIni)}
//return uimsg('hola')
//list of tests to be performed by the comparator routine.
// You can add more or comment the ones you don't need (in my case it depends on the maps)
tests = [
[ ' .id ' ,{a,b -> a.id == b.id} ],
[ ' .text ' ,{a,b -> a.text == b.text} ],
[ ' .details?.htmlText ' ,{a,b -> a.details?.htmlText == b.details?.htmlText} ],
[ ' .note?.plain ' ,{a,b -> a.note?.plain == b.note?.plain} ],
// [ ' .note?.html ' ,{a,b -> a.note?.html == b.note?.html} ],
[ ' .children.size() ' ,{a,b -> a.children.size() == b.children.size()} ],
[ ' .children*.text ' ,{a,b -> a.children*.text == b.children*.text} ],
[ ' .attributes.size() ' ,{a,b -> a.attributes.size() == b.attributes.size()} ],
[ ' .attributes.names.sort() ' ,{a,b -> a.attributes.names.sort() == b.attributes.names.sort()} ],
[ ' .attributes.getAll(nom) ' ,{a,b -> (a.attributes.names + b.attributes.names).unique().sort().every
{ a.attributes.getAll(it)*.toString().sort() == b.attributes.getAll(it)*.toString().sort() }
}],
[ ' .style.name ' ,{a,b -> a.style.name == b.style.name} ],
[ ' .link.text ' ,{a,b -> a.link.text == b.link.text} ],
[ ' .icons.icons.sort() ' ,{a,b -> ([] + a.icons.icons).sort() ==([] + b.icons.icons).sort()} ], //icons (no specific order)
[ ' .icons.icons ' ,{a,b -> a.icons.icons == b.icons.icons} ], //icons in specific order
[ ' .parent.getChildPosition(a) ' ,{a,b -> a.isRoot() || (a.parent.getChildPosition(a) == b.parent.getChildPosition(b)) } ]
]
isDifferentAttr = 'isDifferent'
//def (a,b) = c.selecteds
(a,b) = getRootsFromOpenMapsToCompare(mapsFilter)
//return "$a $b"
a.find{n -> n.attributes.containsKey(isDifferentAttr)}.each{n-> n[isDifferentAttr] = null}
b.find{n -> n.attributes.containsKey(isDifferentAttr)}.each{n-> n[isDifferentAttr] = null}
//println a.text
//println b.text
//println equals(a,b)
def mapsAreEqual = comparar(a,b)
ui.informationMessage("${!mapsAreEqual?'No':mapsAreEqual} different nodes encountered in compared maps")
def comparar(x,y){
def qDiff = 0
if(!equals(x,y)){
qDiff++
print "${x.text} --> "
def resp = equals(x,y, true)
x[isDifferentAttr] = resp
x.attributes.optimizeWidths()
if(y){
y[isDifferentAttr] = resp
y.attributes.optimizeWidths()
}
def msg = "difference encountered in node:\n'${x.pathToRoot*.text.join('\' | \'')}'\n\ndifference: $resp"
if (uimsg(msg) != 0){
selectDifferentNodes(x,y)
assert false
}
}
//se debe comentar la opción 1 o 2 dependiendo lógica que se desee
//TODO: meter en la lógica si se desea comparar id contra id o hijo contra hijo
//TODO: o si se comparan ids vs ids de un lado y del otro:
// intersección: comparar nodos en ambos mapas
// resta: marcar como únicos los de cada mapa
def xChilds = x.children
//def yChilds = //opción 1: compara hijo a hijo
for(def i=0 ; i< xChilds.size(); i++){
def xN = xChilds[i]
def yN = y?.children?[i] //opción 1: compara hijo a hijo
//def yN = b.mindMap.root.find{it.id == xN.id}[0] //opción 2: compara hijo contra igual id
qDiff += comparar(xN,yN)
}
return qDiff
}
def equals(x,y,doLogPrint = false){
def test = y?true:false
def i = 0
def iMax = tests.size()
while(test && i<iMax){
test = tests[i][1](x,y)
i++
}
def msg = i?tests[i-1][0].toString():'it doesn\'t exist in the other map'
if(doLogPrint && !test){
println i + '.- ' + msg + ' ' + test
}
//println i==iMax
return !doLogPrint ? test : msg
}
// returns the root nodes of the two maps to be compared
//( from the first two maps that fullfil the closure filter
def getRootsFromOpenMapsToCompare(Closure filter){
def (map1,map2) = c.openMindMaps.findAll(filter).take(2)
//present to user which are the selected maps
def texto1 = """Maps to be compared
map 1 :
'${map1?.name}'
${map1?.file?.path}
map 2 :
'${map2?.name}'
${map2?.file?.path}
"""
uimsgx(texto1)
return [map1.root, map2.root]
}
def uimsgx(msg){
assert 0 == uimsg(msg)
}
def uimsg(msg){
ui.showConfirmDialog(null, msg.toString(),'Continue?',2)
}
def selectDifferentNodes(x,y){
x ?= a.mindMap.root
c.mapLoader(x.mindMap.file).withView().getMindMap()
sleep(500)
x.pathToRoot.dropRight(1).reverse()*.folded = false
c.centerOnNode(x)
c.select(x)
sleep(500)
y ?= b.mindMap.root
c.mapLoader(y.mindMap.file).withView().getMindMap()
sleep(500)
y.pathToRoot.dropRight(1).reverse()*.folded = false
c.centerOnNode(y)
c.select(y)
sleep(500)
}
def nodos = c.find{n->
n['isDifferent']?true:false
}
seleccionar(nodos)
def seleccionar(nds){
nds.each{n ->
n.pathToRoot*.folded = false
}
c.select(nds)
c.centerOnNode(nds.first())
}
def isDifferentAttr = 'isDifferent'
node.mindMap.root.find{n -> n.attributes.containsKey(isDifferentAttr)}.each{n-> n[isDifferentAttr] = null}
Hope this helps,
edo
Sorry, I just realized this is Freeplane-python-io Discussion. I thought I was answering in the Freeplane's forum.
Hi @EdoFro . No problem ;-) . Maybe @drakes00 will drag some valuable lines from your Java / Groovy script.
You are always very welcome to contribute.
Hi,
I'm working on methods allowing to compare mindmaps by recursively comparing their nodes (eq). For the moment, I'm only comparing their
plaintext
attributes. Can you help me listing which node (and MM) attributes would need comparison so I can PR?Thanks a lot