# Copyright (c) 2012 Santosh Philip
# =======================================================================
# Distributed under the MIT License.
# (See accompanying file LICENSE or copy at
# http://opensource.org/licenses/MIT)
# =======================================================================
"""
Do a diff between two idf files.
Prints the diff in csv or html file format.
You can redirect the output to a file and open the file using as a spreadsheet or by using a browser
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse
from pprint import pprint
import sys
import itertools
try:
from itertools import zip_longest as zip_longest
except:
from itertools import izip_longest as zip_longest
from bs4 import BeautifulSoup, Tag
pathnameto_eplusscripting = "../../"
sys.path.append(pathnameto_eplusscripting)
from eppy.bunch_subclass import BadEPFieldError
from eppy.modeleditor import IDF
from eppy.easyopen import easyopen
from eppy.modeleditor import IDDAlreadySetError
help_message = """
The help message goes here.
"""
[docs]class IDDMismatchError(Exception):
pass
[docs]class Usage(Exception):
def __init__(self, msg):
self.msg = msg
[docs]def getobjname(item):
"""return obj name or blank"""
try:
objname = item.Name
except BadEPFieldError as e:
objname = " "
return objname
[docs]class DtlsSorter(object):
"""helps me to sort it using the order of keys in idd file"""
def __init__(self, dtls):
self.dtlsorder = {j: i for i, j in enumerate(dtls)}
[docs] def getkey(self, item):
return self.dtlsorder[item[0]] # item[0] is the object key
[docs]def makecsvdiffs(thediffs, idf1, idf2):
"""return the csv to be displayed"""
dtls = idf1.model.dtls # undocumented variable
return makecsvdiffs_raw(thediffs, dtls, idf1.idfname, idf2.idfname)
[docs]def makecsvdiffs_raw(thediffs, dtls, n1, n2):
"""return the csv to be displayed - the args here are tricky
This function is called by makecsvdiffs.
Best not to call directly"""
def ishere(val):
if val == None:
return "not here"
else:
return "is here"
rows = []
rows.append(["file1 = %s" % (n1,)])
rows.append(["file2 = %s" % (n2,)])
rows.append("")
rows.append(theheader(n1, n2))
keys = list(thediffs.keys()) # ensures sorting by Name
keys.sort()
# sort the keys in the same order as in the idd
dtlssorter = DtlsSorter(dtls)
keys = sorted(keys, key=dtlssorter.getkey)
for key in keys:
if len(key) == 2:
rw2 = [""] + [ishere(i) for i in thediffs[key]]
else:
rw2 = list(thediffs[key])
rw1 = list(key)
rows.append(rw1 + rw2)
return rows
[docs]def idfdiffs(idf1, idf2):
"""return the diffs between the two idfs"""
# for any object type, it is sorted by name
thediffs = {}
keys = idf1.model.dtls # undocumented variable
for akey in keys:
idfobjs1 = idf1.idfobjects[akey]
idfobjs2 = idf2.idfobjects[akey]
names = set(
[getobjname(i) for i in idfobjs1] + [getobjname(i) for i in idfobjs2]
)
names = sorted(names)
idfobjs1 = sorted(idfobjs1, key=lambda idfobj: idfobj["obj"])
idfobjs2 = sorted(idfobjs2, key=lambda idfobj: idfobj["obj"])
for name in names:
n_idfobjs1 = [item for item in idfobjs1 if getobjname(item) == name]
n_idfobjs2 = [item for item in idfobjs2 if getobjname(item) == name]
for idfobj1, idfobj2 in zip_longest(n_idfobjs1, n_idfobjs2):
if idfobj1 == None:
thediffs[(idfobj2.key.upper(), getobjname(idfobj2))] = (
None,
idf2.idfname,
) # (idf1.idfname, None) -> old
break
if idfobj2 == None:
thediffs[(idfobj1.key.upper(), getobjname(idfobj1))] = (
idf1.idfname,
None,
) # (None, idf2.idfname) -> old
break
for i, (f1, f2) in enumerate(zip(idfobj1.obj, idfobj2.obj)):
if i == 0:
f1, f2 = f1.upper(), f2.upper()
if f1 != f2:
thediffs[
(akey, getobjname(idfobj1), idfobj1.objidd[i]["field"][0])
] = (f1, f2)
return thediffs
[docs]def makecsv(csvdiffs):
"""retun the csv of the diffs"""
lines = []
for row in csvdiffs:
line = ",".join([str(cell) for cell in row])
lines.append(line)
return "\n".join(lines)
[docs]def printcsv(csvdiffs):
"""print the csv"""
for row in csvdiffs:
print(",".join([str(cell) for cell in row]))
[docs]def heading2table(soup, table, row):
"""add heading row to table"""
tr = Tag(soup, name="tr")
table.append(tr)
for attr in row:
th = Tag(soup, name="th")
tr.append(th)
th.append(attr)
[docs]def row2table(soup, table, row):
"""ad a row to the table"""
tr = Tag(soup, name="tr")
table.append(tr)
for attr in row:
td = Tag(soup, name="td")
tr.append(td)
td.append(attr)
[docs]def makehtmlsoup(csvdiffs):
"""make the html soup"""
soup = BeautifulSoup()
html = Tag(soup, name="html")
para1 = Tag(soup, name="p")
para1.append(csvdiffs[0][0])
para2 = Tag(soup, name="p")
para2.append(csvdiffs[1][0])
table = Tag(soup, name="table")
table.attrs.update(dict(border="1"))
soup.append(html)
html.append(para1)
html.append(para2)
html.append(table)
heading2table(soup, table, csvdiffs[3])
for row in csvdiffs[4:]:
row = [str(cell) for cell in row]
row2table(soup, table, row)
# print soup.prettify()
return soup
[docs]def printhtml(csvdiffs):
"""print the html"""
soup = makehtmlsoup(csvdiffs)
print(soup)
[docs]def htmlinnotebook(soup):
"""display the html in jupyter notebook"""
from IPython.core.display import display, HTML
soupstr = str(soup)
display(HTML(soupstr))
if __name__ == "__main__":
# do the argparse stuff
parser = argparse.ArgumentParser(usage=None, description=__doc__)
parser.add_argument(
"file1",
action="store",
help="location of first with idf files = ./somewhere/f1.idf",
)
parser.add_argument(
"file2",
action="store",
help="location of second with idf files = ./somewhere/f2.idf",
)
parser.add_argument(
"--idd",
action="store",
help="location of idd file = ./somewhere/eplusv8-0-1.idd",
)
# parser.add_argument(
# 'idd', action='store',
# help='location of idd file = ./somewhere/eplusv8-0-1.idd')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--csv", action="store_true")
group.add_argument("--html", action="store_true")
nspace = parser.parse_args()
fname1 = nspace.file1
fname2 = nspace.file2
iddfile = nspace.idd
print(iddfile, fname1, fname2)
# IDF.setiddname(iddfile)
idf1 = easyopen(fname1, idd=iddfile)
try:
idf2 = easyopen(fname2, idd=iddfile)
except IDDAlreadySetError as e:
astr = "The two files have different version numners"
raise IDDMismatchError(astr)
# TODO What id they have different idd files ?
thediffs = idfdiffs(idf1, idf2)
csvdiffs = makecsvdiffs(thediffs, idf1, idf2)
if nspace.csv:
printcsv(csvdiffs)
elif nspace.html:
printhtml(csvdiffs)
# python idfdiff.py --csv --idd ../resources/iddfiles/Energy+V7_2_0.idd ../resources/idffiles/V_7_2/constr.idf ../resources/idffiles/V_7_2/constr_diff.idf
# python idfdiff.py --html --idd ../resources/iddfiles/Energy+V7_2_0.idd ../resources/idffiles/V_7_2/constr.idf ../resources/idffiles/V_7_2/constr_diff.idf
# python idfdiff2.py --html --idd ../resources/iddfiles/Energy+V8_0_0.idd ../resources/idffiles/V8_0_0/5ZoneSupRetPlenRAB.idf ../resources/idffiles/V8_0_0/5ZoneWaterLoopHeatPump.idf
# python idfdiff.py --csv ../resources/idffiles/V_7_2/constr.idf ../resources/idffiles/V_7_2/constr_diff.idf
# python idfdiff.py --csv ../resources/idffiles/V_7_2/constr.idf ../resources/idffiles/V_7_2/constr_diff_bad.idf