2016-10-04 11:42:40 -05:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, you can obtain one at http://mozilla.org/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
|
|
|
|
|
|
|
|
# Parses an online.git discovery.xml.
|
|
|
|
class DiscoveryHandler(xml.sax.handler.ContentHandler):
|
|
|
|
def __init__(self):
|
2020-07-28 09:52:14 -05:00
|
|
|
# Dict of app -> {extension -> action}
|
|
|
|
self.appActions = {}
|
2016-10-04 11:42:40 -05:00
|
|
|
self.app = None
|
2020-07-28 09:52:14 -05:00
|
|
|
self.allExtensions = set()
|
2016-10-04 11:42:40 -05:00
|
|
|
def startElement(self, name, attrs):
|
|
|
|
if name == "app":
|
|
|
|
for k, v in list(attrs.items()):
|
|
|
|
if k == "name":
|
|
|
|
self.app = v
|
2020-07-28 09:52:14 -05:00
|
|
|
self.appActions[self.app] = {}
|
|
|
|
elif name == "action" and self.app:
|
|
|
|
action = None
|
|
|
|
ext = None
|
2016-10-04 11:42:40 -05:00
|
|
|
for k, v in list(attrs.items()):
|
|
|
|
if k == "name":
|
2020-07-28 09:52:14 -05:00
|
|
|
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)
|
2016-10-04 11:42:40 -05:00
|
|
|
def endElement(self, name):
|
2020-07-28 09:52:14 -05:00
|
|
|
if name == "app" and self.app:
|
|
|
|
self.app = None
|
2016-10-04 11:42:40 -05:00
|
|
|
|
|
|
|
# Parses core.git filter/source/config/fragments/types/*.xcu.
|
|
|
|
class FilterTypeHandler(xml.sax.handler.ContentHandler):
|
|
|
|
def __init__(self):
|
|
|
|
self.name = None
|
2016-10-05 04:46:28 -05:00
|
|
|
self.inExtensions = False
|
2016-10-04 11:42:40 -05:00
|
|
|
self.content = []
|
2016-10-05 04:46:28 -05:00
|
|
|
self.extensions = None
|
2020-07-28 09:52:14 -05:00
|
|
|
self.extensionsSep = " "
|
2016-10-04 11:42:40 -05:00
|
|
|
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()):
|
2020-07-28 09:52:14 -05:00
|
|
|
if k == "oor:name" and v == "Extensions":
|
2016-10-05 04:46:28 -05:00
|
|
|
self.inExtensions = True
|
2020-07-28 09:52:14 -05:00
|
|
|
elif name == "value" and self.inExtensions:
|
|
|
|
for k, v in list(attrs.items()):
|
|
|
|
if k == "oor:separator":
|
|
|
|
self.extensionsSep = v
|
2016-10-04 11:42:40 -05:00
|
|
|
def endElement(self, name):
|
2020-07-28 09:52:14 -05:00
|
|
|
if name == "prop" and self.inExtensions:
|
2016-10-05 04:46:28 -05:00
|
|
|
self.inExtensions = False
|
2020-07-28 09:52:14 -05:00
|
|
|
self.extensions = "".join(self.content).strip().encode("utf-8").split(self.extensionsSep)
|
|
|
|
self.extensionsSep = " "
|
2016-10-05 04:46:28 -05:00
|
|
|
self.content = []
|
2016-10-04 11:42:40 -05:00
|
|
|
def characters(self, content):
|
2020-07-28 09:52:14 -05:00
|
|
|
if self.inExtensions:
|
2016-10-04 11:42:40 -05:00
|
|
|
self.content.append(content)
|
|
|
|
|
|
|
|
# Parses core.git filter/source/config/fragments/filters/*.xcu.
|
|
|
|
class FilterFragmentHandler(xml.sax.handler.ContentHandler):
|
|
|
|
def __init__(self):
|
|
|
|
self.inType = False
|
|
|
|
self.typeName = None
|
|
|
|
self.inFlags = False
|
|
|
|
self.flags = None
|
2020-07-28 09:52:14 -05:00
|
|
|
self.inDocumentService = False
|
|
|
|
self.documentService = None
|
2016-10-04 11:42:40 -05:00
|
|
|
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
|
2020-07-28 09:52:14 -05:00
|
|
|
elif k == "oor:name" and v == "DocumentService":
|
|
|
|
self.inDocumentService = True
|
2016-10-04 11:42:40 -05:00
|
|
|
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 = []
|
2020-07-28 09:52:14 -05:00
|
|
|
elif name == "prop" and self.inDocumentService:
|
|
|
|
self.inDocumentService = False
|
|
|
|
self.documentService = "".join(self.content).strip()
|
|
|
|
self.content = []
|
2016-10-04 11:42:40 -05:00
|
|
|
def characters(self, content):
|
2020-07-28 09:52:14 -05:00
|
|
|
if self.inType or self.inFlags or self.inDocumentService:
|
2016-10-04 11:42:40 -05:00
|
|
|
self.content.append(content)
|
|
|
|
|
2020-07-28 09:52:14 -05:00
|
|
|
# Builds a 'document service' -> {'extension' -> 'filter flags'} dictionary.
|
|
|
|
def getExtensionProperties(filterDir):
|
|
|
|
# Build a 'type name' -> 'extensions' dictionary.
|
|
|
|
typeNameExtensions = {}
|
2016-10-04 11:42:40 -05:00
|
|
|
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))
|
2020-07-28 09:52:14 -05:00
|
|
|
# Did we find some extensions?
|
|
|
|
if filterTypeHandler.extensions:
|
|
|
|
typeNameExtensions[filterTypeHandler.name] = filterTypeHandler.extensions
|
|
|
|
# Build a 'type name' -> ('filter flag list', 'document service') dictionary.
|
2016-10-04 11:42:40 -05:00
|
|
|
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))
|
2020-07-28 09:52:14 -05:00
|
|
|
if "IMPORT" in handler.flags:
|
|
|
|
if handler.typeName in typeNameFlags:
|
|
|
|
if "EXPORT" in typeNameFlags[handler.typeName][0]:
|
|
|
|
continue # don't modify a filetype with maximal capabilities
|
|
|
|
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',
|
2016-10-04 11:42:40 -05:00
|
|
|
}
|
2020-07-28 09:52:14 -05:00
|
|
|
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
|
2016-10-04 11:42:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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]
|
2020-07-28 09:52:14 -05:00
|
|
|
filterDir = sys.argv[2]
|
2016-10-04 11:42:40 -05:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2020-07-28 09:52:14 -05:00
|
|
|
# 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")
|
2016-10-04 11:42:40 -05:00
|
|
|
continue
|
2020-07-28 09:52:14 -05:00
|
|
|
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]
|
2016-10-04 11:42:40 -05:00
|
|
|
if "IMPORT" in flags and "EXPORT" in flags:
|
|
|
|
coreAction = "edit"
|
|
|
|
else:
|
|
|
|
coreAction = "view"
|
|
|
|
|
|
|
|
if discoveryAction != coreAction:
|
|
|
|
# Inconsistency found.
|
2020-07-28 09:52:14 -05:00
|
|
|
print("warning: action for '" + app + ":" + extension + "' " +
|
2016-10-04 11:42:40 -05:00
|
|
|
"is '" + discoveryAction + "', " +
|
|
|
|
"but it should be '" + coreAction + "'")
|
|
|
|
|
2020-07-28 09:52:14 -05:00
|
|
|
# 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
|
|
|
|
|
2016-10-05 04:46:28 -05:00
|
|
|
# Now see if there are any new types in the core.git filter config which
|
|
|
|
# are missing.
|
2020-07-28 09:52:14 -05:00
|
|
|
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
|
2016-10-05 04:46:28 -05:00
|
|
|
|
|
|
|
# Produce a copy&paste-able XML output for the proposed changes.
|
2020-07-28 09:52:14 -05:00
|
|
|
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 + '"/>')
|
2016-10-05 04:46:28 -05:00
|
|
|
print(' </app>')
|
|
|
|
|
2016-10-04 11:42:40 -05:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|
|
|
|
|
|
|
|
# vim:set shiftwidth=4 softtabstop=4 expandtab:
|