--- /dev/null
+#!/usr/bin/python
+import glob, os, sys, string, xml.sax, getopt
+
+
+__doc__ = """\
+List properties of FlightGear's <PropertyList> XML files.
+
+Usage:
+ lsprop -h
+ lsprop [-v] [-i|-I] [-f <format>] [<list-of-xml-files>]
+
+Options:
+ -h, --help this help screen
+ -v, --verbose increase verbosity (can be used multiple times)
+ -i, --all-indices also show null indices in properties
+ -I, --no-indices don't show any indices in properties
+ -f, --format set output format (default: --format="%f +%l: %p = '%'v'")
+
+Format:
+ %f file path
+ %l line number
+ %c column number
+ %p property path
+ %t property type
+ %V raw value (unescaped)
+ %v cooked value (carriage return, non printable chars etc. escaped)
+ %'v like %v, but single quotes escaped to \\'
+ %"v like %v, but double quotes escaped to \\"
+ %% percent sign
+
+Environment:
+ FG_ROOT
+ FG_HOME
+ LSPROP_FORMAT overrides default format
+
+If no file arguments are specified, then the following files are assumed:
+ $FG_ROOT/preferences.xml
+ $FG_ROOT/Aircraft/*/*-set.xml
+"""
+
+
+class config:
+ root = "/usr/local/share/FlightGear"
+ home = os.environ["HOME"] + "/.fgfs"
+ format = "%f +%l: %p = '%'v'"
+ verbose = 1
+ indices = 1 # 0: no indices; 1: only indices != [0]; 2: all indices
+
+
+def errmsg(msg, color = "31;1"):
+ if os.isatty(2):
+ print >>sys.stderr, "\033[%sm%s\033[m" % (color, msg)
+ else:
+ print >>sys.stderr, msg
+
+
+class Error(Exception):
+ pass
+
+
+class Abort(Exception):
+ pass
+
+
+class XMLError(Exception):
+ def __init__(self, locator, msg):
+ msg = "%s\n\tin %s, line %d, column %d" \
+ % (msg, locator.getSystemId(), locator.getLineNumber(), \
+ locator.getColumnNumber())
+ raise Error(msg)
+
+
+class parse_xml_file(xml.sax.handler.ContentHandler):
+ def __init__(self, path, nesting = 0, stack = None):
+ self.level = 0
+ self.path = path
+ self.nesting = nesting
+ self.type = None
+ if stack:
+ self.stack = stack
+ else:
+ self.stack = [[None, None, {}, ""]] # name, index, indices, data
+
+ self.pretty_path = os.path.normpath(path)
+ if path.startswith(config.root):
+ self.pretty_path = self.pretty_path.replace(config.root, "$FG_ROOT", 1)
+ elif path.startswith(config.home):
+ self.pretty_path = self.pretty_path.replace(config.home, "$FG_HOME", 1)
+
+ if config.verbose > 1:
+ errmsg("%s (%d)" % (path, nesting), "35")
+ if not os.path.exists(path):
+ raise Error("file doesn't exist: " + path)
+ xml.sax.parse(path, self, self)
+
+ def startElement(self, name, attrs):
+ self.level += 1
+ if self.level == 1:
+ if name != "PropertyList":
+ raise XMLError(self.locator, "XML file isn't a <PropertyList>")
+ return
+
+ if attrs.has_key("n"):
+ index = int(attrs["n"])
+ elif name in self.stack[-1][2]:
+ index = self.stack[-1][2][name] + 1
+ else:
+ index = 0
+ self.stack[-1][2][name] = index
+
+ if attrs.has_key("include"):
+ path = os.path.dirname(self.path) + "/" + attrs["include"]
+ if attrs.has_key("omit-node") and attrs["omit-node"] == "y":
+ self.stack.append([None, None, self.stack[-1][2], ""])
+ else:
+ self.stack.append([name, index, {}, ""])
+ parse_xml_file(path, self.nesting + 1, self.stack)
+ else:
+ self.stack.append([name, index, {}, ""])
+
+ self.type = "unspecified"
+ if attrs.has_key("type"):
+ self.type = attrs["type"]
+
+ def endElement(self, name):
+ if not len(self.stack[-1][2]):
+ path = self.pathname()
+ if path:
+ value = self.stack[-1][3]
+ cooked_value = self.escape(value.encode("iso-8859-15", "backslashreplace"))
+ try:
+ print config.cooked_format % {
+ "f": self.pretty_path,
+ "l": self.locator.getLineNumber(),
+ "c": self.locator.getColumnNumber(),
+ "p": path,
+ "t": self.type,
+ "V": value,
+ "v": cooked_value,
+ "v'": cooked_value.replace("'", "\\'"),
+ 'v"': cooked_value.replace('"', '\\"'),
+ }
+ except TypeError, e:
+ raise Abort("invalid --format value")
+
+ elif len(string.strip(self.stack[-1][3])):
+ raise XMLError(self.locator, "child data '" + string.strip(self.stack[-1][3]) + "'")
+
+ self.level -= 1
+ if self.level:
+ self.stack.pop()
+
+ def characters(self, data):
+ self.stack[-1][3] += data
+
+ def setDocumentLocator(self, locator):
+ self.locator = locator
+
+ def pathname(self):
+ path = ""
+ for e in self.stack[1:]:
+ if e[0] == None: # omit-node
+ continue
+ path += "/" + e[0]
+ if e[1] and config.indices == 1 or config.indices == 2:
+ path += "[%d]" % e[1]
+ return path
+
+ def escape(self, string):
+ s = ""
+ for c in string:
+ if c == '\n':
+ s += '\\n'
+ elif c == '\r':
+ s += '\\r'
+ elif c == '\v':
+ s += '\\v'
+ elif c == '\\':
+ s += '\\\\'
+ elif not c.isalnum() and " \t!@#$%^&*()_+|~-=\`[]{};':\",./<>?".find(c) < 0:
+ s += "\\x%02x" % ord(c)
+ else:
+ s += c
+ return s
+
+ def warning(self, exception):
+ raise XMLError(self.locator, "WARNING: " + str(exception))
+ def error(self, exception):
+ raise XMLError(self.locator, "ERROR: " + str(exception))
+ def fatalError(self, exception):
+ raise XMLError(self.locator, "FATAL: " + str(exception))
+
+
+def main():
+ if 'FG_ROOT' in os.environ:
+ config.root = os.environ['FG_ROOT'].rstrip("/\\\t ").lstrip()
+ if 'FG_HOME' in os.environ:
+ config.home = os.environ['FG_HOME'].rstrip("/\\\t ").lstrip()
+ if 'LSPROP_FORMAT' in os.environ:
+ config.format = os.environ['LSPROP_FORMAT']
+
+ # options
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], \
+ "hviIf:", \
+ ["help", "verbose", "all-indices", "no-indices", "format="])
+ except getopt.GetoptError, msg:
+ print >>sys.stderr, str(msg)
+ return 0
+
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ print __doc__
+ print "Current settings:"
+ print '\t--format="%s"' % config.format.replace('"', '\\"')
+ return 0
+ if o in ("-v", "--verbose"):
+ config.verbose += 1
+ if o in ("-i", "--all-indices"):
+ config.indices = 2
+ if o in ("-I", "--no-indices"):
+ config.indices = 0
+ if o in ("-f", "--format"):
+ config.format = a
+
+ # format
+ f = config.format
+ f = f.replace("\\e", "\x1b")
+ f = f.replace("\\033", "\x1b")
+ f = f.replace("\\x1b", "\x1b")
+ f = f.replace("%%", "\x01\x01")
+ f = f.replace("%(", "\x01\x01(")
+ f = f.replace("%f", "\x01(f)s")
+ f = f.replace("%l", "\x01(l)d")
+ f = f.replace("%c", "\x01(c)d")
+ f = f.replace("%p", "\x01(p)s")
+ f = f.replace("%t", "\x01(t)s")
+ f = f.replace("%V", "\x01(V)s")
+ f = f.replace("%v", "\x01(v)s")
+ f = f.replace("%'v", "\x01(v')s")
+ f = f.replace('%"v', '\x01(v")s')
+ f = f.replace("%", "%%")
+ f = f.replace("\x01", "%")
+ config.cooked_format = f
+
+ if config.verbose > 1:
+ print >>sys.stderr, "internal format = [%s]" % config.cooked_format
+
+ if not len(args):
+ args = [config.root + "/preferences.xml"]
+ if not os.path.exists(args[0]):
+ errmsg("Error: environment variable FG_ROOT not set or set wrongly?")
+ return -1
+ for f in glob.glob(config.root + '/Aircraft/*/*-set.xml'):
+ args.append(f)
+
+ for arg in args:
+ try:
+ parse_xml_file(arg)
+ except Abort, e:
+ errmsg("Abort: " + e.args[0])
+ return -1
+ except Error, e:
+ errmsg("Error: " + e.args[0])
+ except IOError, (errno, msg):
+ errmsg("Error: " + msg)
+ return errno
+ except KeyboardInterrupt:
+ print >>sys.stderr, "\033[m"
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())