collabora-online/wsd/lint-discovery.py

325 lines
12 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright the Collabora Online contributors.
#
# SPDX-License-Identifier: MPL-2.0
#
# Makes sure that discovery.xml in online.git is in sync with
# filter/source/config/fragments/ in core.git.
from __future__ import print_function
import os
import sys
import xml.sax
class DiscoveryHandler(xml.sax.handler.ContentHandler):
"""Parses an online.git discovery.xml."""
def __init__(self):
# Dict of app -> {extension -> action}
self.appActions = {}
self.app = None
self.allExtensions = set()
def startElement(self, name, attrs):
if name == "app":
for k, v in list(attrs.items()):
if k == "name":
self.app = v
self.appActions[self.app] = {}
elif name == "action" and self.app:
action = None
ext = None
for k, v in list(attrs.items()):
if k == "name":
action = v
elif k == "ext":
ext = v
if action and ext:
self.appActions[self.app][ext] = action
if ext in self.allExtensions:
# Potential problem:
# see 2de5017e329ce09efbd8f4dc6066fdba3e2c080c
# discovery.xml with duplicating "ext" is valid, but
# can't be used directly in e.g. SharePoint, unless
# specific extensions are imported using
# New-SPWOPIBinding's parameters, avoiding
# the duplication.
print("warning: extension '" + ext +
"' exists for '" + self.app + "', " +
"but already used earlier in discovery.xml")
self.allExtensions.add(ext)
def endElement(self, name):
if name == "app" and self.app:
self.app = None
class FilterTypeHandler(xml.sax.handler.ContentHandler):
"""Parses core.git filter/source/config/fragments/types/*.xcu."""
def __init__(self):
self.name = None
self.inExtensions = False
self.content = []
self.extensions = None
self.extensionsSep = " "
def startElement(self, name, attrs):
if name == "node":
for k, v in list(attrs.items()):
if k == "oor:name":
self.name = v
elif name == "prop":
for k, v in list(attrs.items()):
if k == "oor:name" and v == "Extensions":
self.inExtensions = True
elif name == "value" and self.inExtensions:
for k, v in list(attrs.items()):
if k == "oor:separator":
self.extensionsSep = v
def endElement(self, name):
if name == "prop" and self.inExtensions:
self.inExtensions = False
self.extensions = "".join(self.content).strip()\
.encode("utf-8").split(self.extensionsSep)
self.extensionsSep = " "
self.content = []
def characters(self, content):
if self.inExtensions:
self.content.append(content)
class FilterFragmentHandler(xml.sax.handler.ContentHandler):
"""Parses core.git filter/source/config/fragments/filters/*.xcu."""
def __init__(self):
self.inType = False
self.typeName = None
self.inFlags = False
self.flags = None
self.inDocumentService = False
self.documentService = None
self.content = []
def startElement(self, name, attrs):
if name == "prop":
for k, v in list(attrs.items()):
if k == "oor:name" and v == "Type":
self.inType = True
elif k == "oor:name" and v == "Flags":
self.inFlags = True
elif k == "oor:name" and v == "DocumentService":
self.inDocumentService = True
def endElement(self, name):
if name == "prop" and self.inType:
self.inType = False
self.typeName = "".join(self.content).strip()
self.content = []
elif name == "prop" and self.inFlags:
self.inFlags = False
encodedFlags = "".join(self.content).strip().encode("utf-8")
self.flags = encodedFlags.split(" ")
self.content = []
elif name == "prop" and self.inDocumentService:
self.inDocumentService = False
self.documentService = "".join(self.content).strip()
self.content = []
def characters(self, content):
if self.inType or self.inFlags or self.inDocumentService:
self.content.append(content)
# Builds a 'document service' -> {'extension' -> 'filter flags'} dictionary.
def getExtensionProperties(filterDir):
# Build a 'type name' -> 'extensions' dictionary.
typeNameExtensions = {}
typeFragments = os.path.join(filterDir, "types")
for typeFragment in os.listdir(typeFragments):
if not typeFragment.endswith(".xcu"):
continue
parser = xml.sax.make_parser()
filterTypeHandler = FilterTypeHandler()
parser.setContentHandler(filterTypeHandler)
parser.parse(os.path.join(typeFragments, typeFragment))
# Did we find some extensions?
if filterTypeHandler.extensions:
typeNameExtensions[filterTypeHandler.name] = \
filterTypeHandler.extensions
# Build a 'type name' -> ('filter flag list', 'document service')
# dictionary.
typeNameFlags = {}
filterFragments = os.path.join(filterDir, "filters")
for filterFragment in os.listdir(filterFragments):
if not filterFragment.endswith(".xcu"):
continue
parser = xml.sax.make_parser()
handler = FilterFragmentHandler()
parser.setContentHandler(handler)
parser.parse(os.path.join(filterFragments, filterFragment))
if "IMPORT" in handler.flags:
if handler.typeName in typeNameFlags:
if "EXPORT" in typeNameFlags[handler.typeName][0]:
# don't modify a filetype with maximal capabilities
continue
typeNameFlags[handler.typeName] = \
(handler.flags, handler.documentService)
# Now build the combined
# 'document service' -> {'extension' -> 'filter flags'}.
extensionProperties = {}
for typeName in typeNameExtensions:
if typeName not in typeNameFlags:
continue
flags, documentService = typeNameFlags[typeName]
if documentService not in extensionProperties:
extensionProperties[documentService] = {}
for extension in typeNameExtensions[typeName]:
extensionProperties[documentService][extension] = flags
return extensionProperties
# Map app names to document service names
appDocumentServices = {
'writer': 'com.sun.star.text.TextDocument',
'writer-global': 'com.sun.star.text.GlobalDocument',
'writer-web': 'com.sun.star.text.WebDocument',
'calc': 'com.sun.star.sheet.SpreadsheetDocument',
'impress': 'com.sun.star.presentation.PresentationDocument',
'draw': 'com.sun.star.drawing.DrawingDocument',
}
documentServicesApp = {v: k for k, v in appDocumentServices.items()}
# We know about these extensions
extensionsSkipList = {
'xls', # we know that it can be edited
'pdf', # it exists for draw - its entry in core.git is missing document
# service
'zip',
'htm',
'html',
'xhtml',
'*', # well, obvious ;-)
'', # and this :-D
}
def main():
discoveryXml = "discovery.xml"
repoGuess = os.path.join(os.environ["HOME"], "git/libreoffice/master")
filterDir = os.path.join(repoGuess, "filter/source/config/fragments")
if len(sys.argv) >= 3:
discoveryXml = sys.argv[1]
filterDir = sys.argv[2]
# Parse discovery.xml, which describes what online.git exposes at the
# moment.
parser = xml.sax.make_parser()
discoveryHandler = DiscoveryHandler()
parser.setContentHandler(discoveryHandler)
parser.parse(discoveryXml)
# Parse core.git filter definitions to build a
# 'document service' -> {'extension' -> 'filter flags'} dictionary.
extensionProperties = getExtensionProperties(filterDir)
proposed = {}
# Now look up the filter flags in core.git for the extension.
for app, actions in discoveryHandler.appActions.items():
if app not in appDocumentServices:
continue # e.g., for "Capabilities"
documentService = appDocumentServices[app]
if documentService not in extensionProperties:
# Inconsistency found.
print("warning: actions for '" + app + "' " +
"exist, but not found in core.git")
continue
for extension, discoveryAction in actions.items():
if extension in extensionsSkipList:
continue
if extension not in extensionProperties[documentService]:
# Inconsistency found.
print("warning: action for '" + app + ":" + extension + "' " +
"exists, but is not found in core.git")
continue
flags = extensionProperties[documentService][extension]
if "IMPORT" in flags and "EXPORT" in flags:
coreAction = "edit"
else:
coreAction = "view"
if discoveryAction != coreAction:
# Inconsistency found.
print("warning: action for '" + app + ":" + extension + "' " +
"is '" + discoveryAction + "', " +
"but it should be '" + coreAction + "'")
# Now see if there are any new extensions in the core.git filter config
# which are missing.
for extension, flags in extensionProperties[documentService].items():
if extension not in actions:
if "IMPORT" in flags and "EXPORT" in flags:
action = "edit"
else:
action = "view"
if app not in proposed:
proposed[app] = {}
proposed[app][extension] = action
# Now see if there are any new types in the core.git filter config which
# are missing.
for documentService, extensions in extensionProperties.items():
missingName = None
if documentService not in documentServicesApp:
# Inconsistency found.
print("warning: extensions for '" + documentService + "' " +
"found in core.git, without mapping to apps "
"in discovery.xml")
missingName = documentService
else:
app = documentServicesApp[documentService]
if app not in discoveryHandler.appActions:
# Inconsistency found.
print("warning: extensions for '" + app + "' " +
"found in core.git, all missing in discovery.xml")
missingName = app
if missingName:
for extension, flags in extensions.items():
if "IMPORT" in flags and "EXPORT" in flags:
action = "edit"
else:
action = "view"
if missingName not in proposed:
proposed[missingName] = {}
proposed[missingName][extension] = action
# Produce a copy&paste-able XML output for the proposed changes.
for app, extensions in proposed.items():
newExtensions = {}
for extension, action in extensions.items():
if extension in extensionsSkipList:
continue
if extension in discoveryHandler.allExtensions:
continue # see 2de5017e329ce09efbd8f4dc6066fdba3e2c080c
newExtensions[extension] = action
if not newExtensions:
continue # no extensions after filtering
print(' <app name="' + app + '">')
for extension, action in newExtensions.items():
print(' <action name="' + action + '" default="true" ' +
'ext="' + extension + '"/>')
print(' </app>')
if __name__ == "__main__":
main()
# vim:set shiftwidth=4 softtabstop=4 expandtab: