3
# This file is part of Diamond.
5
# Diamond is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
10
# Diamond is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
31
class DiamondSchemaError:
32
def __init__(self, parent, gladefile, schema_file, schematron_file):
34
self.gladefile = gladefile
35
self.schema_file = schema_file
36
self.schematron_file = schematron_file
40
self.listwindow = self.parent.gui.get_widget("errListWindow")
41
self.listview = self.parent.gui.get_widget("sch_err_list")
43
if self.listwindow is None or self.listview is None:
44
raise Exception("Could not find the error list widgets")
46
self.model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
47
self.cellcombo = gtk.CellRendererCombo()
48
self.cellcombo.set_property("model", self.model)
49
self.cellcombo.set_property("editable", False)
51
column_renderer = gtk.CellRendererText()
52
column_renderer.set_property("editable", False)
53
column_xpath = gtk.TreeViewColumn("Element", column_renderer, text = 0)
54
column_error = gtk.TreeViewColumn("Error", column_renderer, text = 1)
56
self.listview.append_column(column_xpath)
57
self.listview.append_column(column_error)
59
column_xpath.set_property("expand", True)
60
column_xpath.set_resizable(True)
61
column_xpath.set_property("min-width", 75)
63
column_error.set_property("expand", True)
64
column_error.set_resizable(True)
65
column_error.set_property("min-width", 75)
67
# Set adjustment parameters for the list view
68
self.listview.set_model(self.model)
69
self.listview.connect("row_activated", self.on_xml_row_activate)
71
# User can only select one error at a time to go to.
72
self.listview.get_selection().set_mode(gtk.SELECTION_SINGLE)
75
self.listwindow.hide()
77
# Create the error list, model and tree view columns if the list does not exist.
78
def create_error_list(self):
79
if self.listview is not None:
82
# Destroy the error list widgets
83
def destroy_error_list(self):
84
if self.listview is not None:
86
self.listwindow.hide()
88
def on_validate(self, widget=None):
90
Tools > Validate XML. This writes out the XML to a temporary file, then calls
91
xmllint on it. (I didn't use the Xvif validation capabilities because the error
92
messages are worse than useless; xmllint actually gives line numbers and such).
95
if self.schema_file is None:
96
dialogs.error(self.parent.main_window, "No schema file open")
99
self.tmp = tempfile.NamedTemporaryFile()
100
self.parent.tree.write(self.tmp.name)
102
std_input, std_output, std_error = os.popen3("xmllint --relaxng %s %s --noout --path \".\"" % (self.schema_file, self.tmp.name))
103
output = std_error.read()
105
output = output.replace("%s fails to validate" % self.tmp.name, "")
107
if output.strip() == "%s validates" % self.tmp.name:
108
# No errors. Close the error list (if it is open) and display a congratulatory message box.
109
self.destroy_error_list()
110
dialogs.message_box(self.parent.main_window, "XML file validated successfully", "Validation successful")
112
self.create_error_list()
114
self.errlist_type = 1
117
lines = output.split("\n")
119
# Read the temporary output file, split it by lines and use it to convert
120
# line numbers to xpaths for the column data.
121
f = file(self.tmp.name)
123
output_lines = data.split("\n")
129
tokens = line.split(" ")
131
# Parse each line of the xmllint --relaxng output.
133
# ELEMENT:LINE: element NAME: Relax-NG validity error : ERROR MESSAGE
134
# (Capitals denotes variable data). Update the following code if the output changes.
135
sub_tokens = tokens[0].split(":")
137
message = " ".join(tokens[7:])
140
xpath = self.add_to_xpath(output_lines, "", long(line)-1)
142
self.model.append([ xpath, message ])
144
self.listview.set_property("visible", True)
145
self.listwindow.set_property("visible", True)
147
# Update the status bar. Inform the user of the number of errors.
148
self.parent.statusbar.set_statusbar("There are %u Relax-NG errors in the document" % len(lines))
152
def on_validate_schematron(self, widget=None):
154
Tools > Validate Schematron. This uses the etree.Schematron API, if it exists, to
155
validate the document tree against a supplied schematron file.
158
if self.schematron_file is None:
159
dialogs.error(self.parent.main_window, "No schematron file supplied")
162
# Write the current version out to file.
163
tmp = tempfile.NamedTemporaryFile()
164
self.parent.tree.write(tmp.name)
165
std_input, std_output, err_output = os.popen3("xmllint --schematron %s %s --noout" % (self.schematron_file, tmp.name))
166
output = err_output.read()
168
output = output.replace("%s fails to validate" % tmp.name, "")
169
output = output.strip()
171
if output == "%s validates" % tmp.name:
172
self.destroy_error_list()
173
dialogs.message_box(self.parent.main_window, "XML file validated successfully against %s" % self.schematron_file, "XML validation successful")
175
# Clear the list, as there may still be errors from a previous schematron validation.
177
self.errlist_type = 0
179
# Add each line of output as a new row.
180
lines = output.split("\n")
186
tokens = line.split(" ")
187
self.model.append([ tokens[0], " ".join(tokens[3:]), 0 ])
189
# Finish set up of list view widget.
190
self.listview.set_property("visible", True)
191
self.listwindow.set_property("visible", True)
192
self.parent.statusbar.set_statusbar("There are %u Schematron errors in the document" % len(lines))
194
def errlist_is_open(self):
195
return self.listview.get_property("visible")
197
def get_elem_name(self, line):
198
xml = re.compile("<([/?!]?\w+)", re.VERBOSE)
199
matches = xml.findall(line)
200
# print "get_elem_name", line, "=", matches[0]
203
def add_to_xpath(self, lines, xpath, line_no):
205
line = lines[line_no].strip()
206
name = self.get_elem_name(line)
208
if name not in ["string_value", "integer_value", "real_value"]:
209
xpath = "/" + name + xpath
211
# Are we at the start of the document?
212
if line_no == 0 or line_no == 1:
215
# Find the parent node.
217
line_no = line_no - 1
218
line = lines[line_no].strip()
220
# print "MAIN LOOP: ", line
223
if line.find("-->") != -1:
224
while line.find("<!--") == -1:
225
line_no = line_no - 1
226
line = lines[line_no].strip()
229
if line.startswith("</"):
230
name = line.strip("<>/")
231
while line.find("<%s" % (name)) == -1:
232
# print "BACK LOOP: ", line
233
line_no = line_no - 1
234
line = lines[line_no].strip()
237
name = self.get_elem_name(line)
238
if name.find("<%s />" % (name)) == -1 and name.find("</ %s>" % (name)):
239
return self.add_to_xpath(lines, xpath, line_no)
241
def scroll_to_xpath(self, xpath):
242
iter = self.parent.get_treestore_iter_from_xmlpath(xpath)
243
path = self.parent.treestore.get_path(iter)
244
self.parent.treeview.expand_to_path(path)
245
self.parent.treeview.get_selection().select_iter(iter)
246
self.parent.treeview.scroll_to_cell(path, use_align=True, col_align=0.5)
249
def on_xml_row_activate(self, treeview, path, view_column):
251
# Get the selected row.
252
selection = treeview.get_selection()
253
(model, iter) = selection.get_selected()
255
# Get the line number
256
xpath = model.get_value(iter, 0)
258
# Use the in-memory schema representation to get the XPath from the name.
259
self.scroll_to_xpath(xpath)