ixbrl-viewer 1.4.15__py3-none-any.whl → 1.4.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ixbrl-viewer might be problematic. Click here for more details.
- iXBRLViewerPlugin/__init__.py +87 -67
- iXBRLViewerPlugin/_version.py +2 -2
- iXBRLViewerPlugin/constants.py +6 -0
- iXBRLViewerPlugin/iXBRLViewer.py +27 -27
- iXBRLViewerPlugin/ui.py +16 -3
- iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
- iXBRLViewerPlugin/xhtmlserialize.py +1 -1
- {ixbrl_viewer-1.4.15.dist-info → ixbrl_viewer-1.4.16.dist-info}/METADATA +1 -1
- {ixbrl_viewer-1.4.15.dist-info → ixbrl_viewer-1.4.16.dist-info}/RECORD +15 -15
- tests/puppeteer/tools/generate.sh +1 -1
- {ixbrl_viewer-1.4.15.dist-info → ixbrl_viewer-1.4.16.dist-info}/LICENSE +0 -0
- {ixbrl_viewer-1.4.15.dist-info → ixbrl_viewer-1.4.16.dist-info}/NOTICE +0 -0
- {ixbrl_viewer-1.4.15.dist-info → ixbrl_viewer-1.4.16.dist-info}/WHEEL +0 -0
- {ixbrl_viewer-1.4.15.dist-info → ixbrl_viewer-1.4.16.dist-info}/entry_points.txt +0 -0
- {ixbrl_viewer-1.4.15.dist-info → ixbrl_viewer-1.4.16.dist-info}/top_level.txt +0 -0
iXBRLViewerPlugin/__init__.py
CHANGED
|
@@ -9,18 +9,20 @@ import sys
|
|
|
9
9
|
import tempfile
|
|
10
10
|
import traceback
|
|
11
11
|
from optparse import OptionGroup, OptionParser
|
|
12
|
+
from pathlib import Path
|
|
12
13
|
|
|
13
14
|
from arelle import Cntlr
|
|
14
15
|
from arelle.LocalViewer import LocalViewer
|
|
15
16
|
from arelle.ModelDocument import Type
|
|
17
|
+
from arelle.UrlUtil import isHttpUrl
|
|
16
18
|
from arelle.webserver.bottle import static_file
|
|
17
19
|
|
|
18
|
-
from .constants import CONFIG_FEATURE_PREFIX, CONFIG_LAUNCH_ON_LOAD, \
|
|
19
|
-
CONFIG_SCRIPT_URL, DEFAULT_LAUNCH_ON_LOAD, DEFAULT_OUTPUT_NAME, \
|
|
20
|
-
DEFAULT_JS_FILENAME, DEFAULT_VIEWER_PATH,
|
|
20
|
+
from .constants import CONFIG_COPY_SCRIPT, CONFIG_FEATURE_PREFIX, CONFIG_LAUNCH_ON_LOAD, \
|
|
21
|
+
CONFIG_SCRIPT_URL, DEFAULT_COPY_SCRIPT, DEFAULT_LAUNCH_ON_LOAD, DEFAULT_OUTPUT_NAME, \
|
|
22
|
+
DEFAULT_JS_FILENAME, DEFAULT_VIEWER_PATH, ERROR_MESSAGE_CODE, \
|
|
23
|
+
EXCEPTION_MESSAGE_CODE, FEATURE_CONFIGS
|
|
21
24
|
from .iXBRLViewer import IXBRLViewerBuilder, IXBRLViewerBuilderError
|
|
22
25
|
|
|
23
|
-
|
|
24
26
|
#
|
|
25
27
|
# GUI operation:
|
|
26
28
|
#
|
|
@@ -54,7 +56,18 @@ def iXBRLViewerCommandLineOptionExtender(parser, *args, **kwargs):
|
|
|
54
56
|
action="store",
|
|
55
57
|
dest="viewerURL",
|
|
56
58
|
default=DEFAULT_VIEWER_PATH,
|
|
57
|
-
help="
|
|
59
|
+
help="A filepath or URL to the iXBRL Viewer JavaScript file which will be downloaded or copied into the output directory."
|
|
60
|
+
" If a relative filepath is to be copied it will be resolved relative to the current working directory."
|
|
61
|
+
" If '--viewer-no-copy-script' is used, the '--viewer-url' file will not be copied or downloaded and instead directly referenced from the HTML file."
|
|
62
|
+
" Examples: 'customViewerScript.js', '/path/to/ixbrlviewer.js', 'https://example.com/ixbrlviewer.js'."
|
|
63
|
+
f" The default value is '{DEFAULT_JS_FILENAME}'.")
|
|
64
|
+
parser.add_option("--viewer-no-copy-script",
|
|
65
|
+
action="store_true",
|
|
66
|
+
dest="viewerNoCopyScript",
|
|
67
|
+
default=False,
|
|
68
|
+
help="Prevent copying the iXBRL Viewer's JavaScript file from '--viewer-url' into the output directory."
|
|
69
|
+
" If used, the iXBRL Viewer HTML file will reference the '--viewer-url' directly."
|
|
70
|
+
" It must be a valid script location at the time the viewer is opened in a browser.")
|
|
58
71
|
parser.add_option("--viewer-validation-messages",
|
|
59
72
|
dest="validationMessages",
|
|
60
73
|
action="store_true",
|
|
@@ -102,48 +115,63 @@ def generateViewer(
|
|
|
102
115
|
zipViewerOutput: bool = False,
|
|
103
116
|
features: list[str] | None = None,
|
|
104
117
|
packageDownloadURL: str | None = None,
|
|
105
|
-
copyScript = True
|
|
118
|
+
copyScript: bool = True,
|
|
106
119
|
) -> None:
|
|
107
120
|
"""
|
|
108
|
-
Generate and save
|
|
109
|
-
If
|
|
121
|
+
Generate and save an iXBRL viewer at the given destination (file, directory, or in-memory file) with the given viewer script URL.
|
|
122
|
+
If copyScript is True the viewer script will be copied into the output destination.
|
|
110
123
|
:param cntlr: The arelle controller that contains the model to be included in the viewer
|
|
111
124
|
:param saveViewerDest: The target that viewer data/files will be written to (path to file or directory, or a file object itself).
|
|
112
|
-
:param viewerURL: The filepath or URL location of the
|
|
125
|
+
:param viewerURL: The filepath or URL location of the iXBRL Viewer JavaScript file.
|
|
113
126
|
:param showValidationMessages: True if validation messages should be shown in the viewer.
|
|
114
127
|
:param useStubViewer: True if the stub viewer should be used.
|
|
115
128
|
:param zipViewerOutput: True if the destination is a zip archive.
|
|
116
|
-
:param features:
|
|
129
|
+
:param features: Optional list of features to enable.
|
|
130
|
+
:param packageDownloadURL: Optional URL to use as the report package download URL.
|
|
131
|
+
:param copyScript: Controls if the script referenced by viewerURL is copied into the output directory, or directly set as the 'src' value of the script tag in the HTML iXBRL Viewer.
|
|
117
132
|
"""
|
|
118
133
|
# extend XBRL-loaded run processing for this option
|
|
119
|
-
|
|
134
|
+
abortGenerationMsg = "Skipping iXBRL Viewer generation."
|
|
135
|
+
if not saveViewerDest:
|
|
136
|
+
cntlr.addToLog(f"iXBRL Viewer destination not provided. {abortGenerationMsg}", messageCode=EXCEPTION_MESSAGE_CODE)
|
|
120
137
|
return
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
|
|
139
|
+
if not viewerURL:
|
|
140
|
+
cntlr.addToLog(f"iXBRL Viewer script not provided. {abortGenerationMsg}", messageCode=EXCEPTION_MESSAGE_CODE)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
if (cntlr.modelManager is None
|
|
144
|
+
or len(cntlr.modelManager.loadedModelXbrls) == 0
|
|
123
145
|
or any(not mx.modelDocument for mx in cntlr.modelManager.loadedModelXbrls)):
|
|
124
|
-
cntlr.addToLog("No taxonomy loaded.")
|
|
146
|
+
cntlr.addToLog(f"No taxonomy loaded. {abortGenerationMsg}", messageCode=ERROR_MESSAGE_CODE)
|
|
125
147
|
return
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
cntlr.addToLog("No inline XBRL document loaded.")
|
|
148
|
+
if cntlr.modelManager.modelXbrl.modelDocument.type not in (Type.INLINEXBRL, Type.INLINEXBRLDOCUMENTSET):
|
|
149
|
+
cntlr.addToLog(f"No inline XBRL document loaded. {abortGenerationMsg}", messageCode=ERROR_MESSAGE_CODE)
|
|
129
150
|
return
|
|
151
|
+
|
|
130
152
|
copyScriptPath = None
|
|
131
|
-
if
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
if copyScript:
|
|
154
|
+
originalViewerURL = viewerURL
|
|
155
|
+
viewerPath = None
|
|
156
|
+
if isHttpUrl(originalViewerURL):
|
|
157
|
+
cacheScript = cntlr.webCache.getfilename(originalViewerURL)
|
|
158
|
+
if cacheScript and (cacheScriptPath := Path(cacheScript)).is_file():
|
|
159
|
+
viewerPath = cacheScriptPath
|
|
160
|
+
else:
|
|
161
|
+
downloadFailedErrorMessage = f"Unable to download iXBRL Viewer script '{originalViewerURL}'."
|
|
162
|
+
if cntlr.webCache.workOffline:
|
|
163
|
+
downloadFailedErrorMessage += " Disable offline mode and try again."
|
|
164
|
+
cntlr.addToLog(f"{downloadFailedErrorMessage} {abortGenerationMsg}", messageCode=EXCEPTION_MESSAGE_CODE)
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
if not viewerPath:
|
|
168
|
+
viewerPath = Path(viewerURL)
|
|
169
|
+
copyScriptPath = viewerPath.resolve()
|
|
170
|
+
viewerURL = viewerPath.name
|
|
171
|
+
if not viewerPath.is_file():
|
|
172
|
+
cntlr.addToLog(f"iXBRL Viewer script not found at '{viewerPath}'. {abortGenerationMsg}", messageCode=EXCEPTION_MESSAGE_CODE)
|
|
173
|
+
return
|
|
174
|
+
|
|
147
175
|
try:
|
|
148
176
|
viewerBuilder = IXBRLViewerBuilder(cntlr.modelManager.loadedModelXbrls)
|
|
149
177
|
if features:
|
|
@@ -155,20 +183,8 @@ def generateViewer(
|
|
|
155
183
|
except IXBRLViewerBuilderError as ex:
|
|
156
184
|
print(ex)
|
|
157
185
|
except Exception as ex:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def getAbsoluteViewerPath(saveViewerPath: str, relativeViewerPath: str) -> str:
|
|
162
|
-
"""
|
|
163
|
-
Generate a path to the viewer script given the save destination path as a starting point.
|
|
164
|
-
:param saveViewerPath: Path to file or directory where viewer output will be saved.
|
|
165
|
-
:param relativeViewerPath: Path to save destination relative to viewer save path.
|
|
166
|
-
:return: An absolute file path to the viewer.
|
|
167
|
-
"""
|
|
168
|
-
saveViewerDir = saveViewerPath
|
|
169
|
-
if os.path.isfile(saveViewerDir):
|
|
170
|
-
saveViewerDir = os.path.dirname(os.path.join(os.getcwd(), saveViewerDir))
|
|
171
|
-
return os.path.join(saveViewerDir, relativeViewerPath)
|
|
186
|
+
tb = traceback.format_tb(sys.exc_info()[2])
|
|
187
|
+
cntlr.addToLog(f"Exception {ex} \nTraceback {tb}", messageCode=EXCEPTION_MESSAGE_CODE)
|
|
172
188
|
|
|
173
189
|
|
|
174
190
|
def getFeaturesFromOptions(options: argparse.Namespace | OptionParser):
|
|
@@ -181,35 +197,37 @@ def getFeaturesFromOptions(options: argparse.Namespace | OptionParser):
|
|
|
181
197
|
|
|
182
198
|
def iXBRLViewerCommandLineXbrlRun(cntlr, options, *args, **kwargs):
|
|
183
199
|
generateViewer(
|
|
184
|
-
cntlr,
|
|
185
|
-
options.saveViewerDest or kwargs.get("responseZipStream"),
|
|
186
|
-
options.viewerURL,
|
|
187
|
-
options.
|
|
188
|
-
options.
|
|
189
|
-
options.
|
|
190
|
-
|
|
191
|
-
options
|
|
200
|
+
cntlr=cntlr,
|
|
201
|
+
saveViewerDest=options.saveViewerDest or kwargs.get("responseZipStream"),
|
|
202
|
+
viewerURL=options.viewerURL,
|
|
203
|
+
copyScript=not options.viewerNoCopyScript,
|
|
204
|
+
showValidationMessages=options.validationMessages,
|
|
205
|
+
useStubViewer=options.useStubViewer,
|
|
206
|
+
zipViewerOutput=options.zipViewerOutput,
|
|
207
|
+
features=getFeaturesFromOptions(options),
|
|
208
|
+
packageDownloadURL=options.packageDownloadURL,
|
|
192
209
|
)
|
|
193
210
|
|
|
194
211
|
|
|
195
212
|
def iXBRLViewerSaveCommand(cntlr):
|
|
196
213
|
from .ui import SaveViewerDialog
|
|
197
214
|
if cntlr.modelManager is None or cntlr.modelManager.modelXbrl is None:
|
|
198
|
-
cntlr.addToLog("No document loaded.")
|
|
215
|
+
cntlr.addToLog("No document loaded.", messageCode=ERROR_MESSAGE_CODE)
|
|
199
216
|
return
|
|
200
217
|
modelXbrl = cntlr.modelManager.modelXbrl
|
|
201
218
|
if modelXbrl.modelDocument.type not in (Type.INLINEXBRL, Type.INLINEXBRLDOCUMENTSET):
|
|
202
|
-
cntlr.addToLog("No inline XBRL document loaded.")
|
|
219
|
+
cntlr.addToLog("No inline XBRL document loaded.", messageCode=ERROR_MESSAGE_CODE)
|
|
203
220
|
return
|
|
204
221
|
dialog = SaveViewerDialog(cntlr)
|
|
205
222
|
dialog.render()
|
|
206
223
|
if dialog.accepted and dialog.filename():
|
|
207
224
|
generateViewer(
|
|
208
|
-
cntlr,
|
|
209
|
-
dialog.filename(),
|
|
210
|
-
dialog.scriptUrl() or DEFAULT_VIEWER_PATH,
|
|
225
|
+
cntlr=cntlr,
|
|
226
|
+
saveViewerDest=dialog.filename(),
|
|
227
|
+
viewerURL=dialog.scriptUrl() or DEFAULT_VIEWER_PATH,
|
|
228
|
+
copyScript=dialog.copyScript(),
|
|
211
229
|
zipViewerOutput=dialog.zipViewerOutput(),
|
|
212
|
-
features=dialog.features()
|
|
230
|
+
features=dialog.features(),
|
|
213
231
|
)
|
|
214
232
|
|
|
215
233
|
|
|
@@ -285,18 +303,20 @@ def guiRun(cntlr, modelXbrl, attach, *args, **kwargs):
|
|
|
285
303
|
if cntlr.config.setdefault(f'{CONFIG_FEATURE_PREFIX}{c.key}', False)
|
|
286
304
|
]
|
|
287
305
|
generateViewer(
|
|
288
|
-
cntlr,
|
|
306
|
+
cntlr=cntlr,
|
|
289
307
|
saveViewerDest=tempViewer.name,
|
|
290
308
|
viewerURL=cntlr.config.get(CONFIG_SCRIPT_URL) or DEFAULT_VIEWER_PATH,
|
|
309
|
+
copyScript=cntlr.config.get(CONFIG_COPY_SCRIPT, DEFAULT_COPY_SCRIPT),
|
|
291
310
|
useStubViewer=True,
|
|
292
|
-
features=features
|
|
311
|
+
features=features,
|
|
293
312
|
)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
313
|
+
if Path(tempViewer.name, viewer_file_name).exists():
|
|
314
|
+
localViewer = iXBRLViewerLocalViewer("iXBRL Viewer", os.path.dirname(__file__))
|
|
315
|
+
localhost = localViewer.init(cntlr, tempViewer.name)
|
|
316
|
+
webbrowser.open(f'{localhost}/{viewer_file_name}')
|
|
297
317
|
except Exception as ex:
|
|
298
318
|
modelXbrl.error(
|
|
299
|
-
|
|
319
|
+
EXCEPTION_MESSAGE_CODE,
|
|
300
320
|
"Exception %(exception)s \nTraceback %(traceback)s",
|
|
301
321
|
modelObject=modelXbrl, exception=ex, traceback=traceback.format_tb(sys.exc_info()[2])
|
|
302
322
|
)
|
iXBRLViewerPlugin/_version.py
CHANGED
iXBRLViewerPlugin/constants.py
CHANGED
|
@@ -7,11 +7,17 @@ from .featureConfig import FeatureConfig
|
|
|
7
7
|
CONFIG_FEATURE_PREFIX = 'iXBRLViewerFeature_'
|
|
8
8
|
CONFIG_FILE_DIRECTORY = 'iXBRLViewerFileDir'
|
|
9
9
|
CONFIG_LAUNCH_ON_LOAD = 'iXBRLViewerLaunchOnLoad'
|
|
10
|
+
CONFIG_COPY_SCRIPT = 'iXBRLViewerCopyScript'
|
|
10
11
|
CONFIG_OUTPUT_FILE = 'iXBRLViewerOutputFile'
|
|
11
12
|
CONFIG_SCRIPT_URL = 'iXBRLViewerScriptURL'
|
|
12
13
|
CONFIG_ZIP_OUTPUT = 'iXBRLViewerZipOutput'
|
|
13
14
|
|
|
15
|
+
ERROR_MESSAGE_CODE = 'viewer:error'
|
|
16
|
+
EXCEPTION_MESSAGE_CODE = 'viewer:exception'
|
|
17
|
+
INFO_MESSAGE_CODE = 'viewer:info'
|
|
18
|
+
|
|
14
19
|
DEFAULT_LAUNCH_ON_LOAD = True
|
|
20
|
+
DEFAULT_COPY_SCRIPT = True
|
|
15
21
|
DEFAULT_OUTPUT_NAME = 'ixbrlviewer.html'
|
|
16
22
|
DEFAULT_JS_FILENAME = 'ixbrlviewer.js'
|
|
17
23
|
DEFAULT_VIEWER_PATH = os.path.join(os.path.dirname(__file__), "viewer", "dist", DEFAULT_JS_FILENAME)
|
iXBRLViewerPlugin/iXBRLViewer.py
CHANGED
|
@@ -12,6 +12,7 @@ import urllib.parse
|
|
|
12
12
|
import zipfile
|
|
13
13
|
from collections import defaultdict
|
|
14
14
|
from copy import deepcopy
|
|
15
|
+
from pathlib import Path
|
|
15
16
|
|
|
16
17
|
import pycountry
|
|
17
18
|
from arelle import XbrlConst
|
|
@@ -23,7 +24,7 @@ from arelle.UrlUtil import isHttpUrl
|
|
|
23
24
|
from arelle.ValidateXbrlCalcs import inferredDecimals
|
|
24
25
|
from lxml import etree
|
|
25
26
|
|
|
26
|
-
from .constants import DEFAULT_OUTPUT_NAME,
|
|
27
|
+
from .constants import DEFAULT_JS_FILENAME, DEFAULT_OUTPUT_NAME, ERROR_MESSAGE_CODE, FEATURE_CONFIGS, INFO_MESSAGE_CODE
|
|
27
28
|
from .xhtmlserialize import XHTMLSerializer
|
|
28
29
|
|
|
29
30
|
|
|
@@ -352,7 +353,7 @@ class IXBRLViewerBuilder:
|
|
|
352
353
|
if child.tag == '{http://www.w3.org/1999/xhtml}body':
|
|
353
354
|
for body_child in child:
|
|
354
355
|
if body_child.tag == '{http://www.w3.org/1999/xhtml}script' and body_child.get('type','') == 'application/x.ixbrl-viewer+json':
|
|
355
|
-
self.logger_model.error(
|
|
356
|
+
self.logger_model.error(ERROR_MESSAGE_CODE, "File already contains iXBRL viewer")
|
|
356
357
|
return False
|
|
357
358
|
|
|
358
359
|
child.append(etree.Comment("BEGIN IXBRL VIEWER EXTENSIONS"))
|
|
@@ -395,7 +396,7 @@ class IXBRLViewerBuilder:
|
|
|
395
396
|
|
|
396
397
|
def createViewer(
|
|
397
398
|
self,
|
|
398
|
-
scriptUrl: str =
|
|
399
|
+
scriptUrl: str = DEFAULT_JS_FILENAME,
|
|
399
400
|
useStubViewer: bool = False,
|
|
400
401
|
showValidations: bool = True,
|
|
401
402
|
packageDownloadURL: str | None = None,
|
|
@@ -430,7 +431,7 @@ class IXBRLViewerBuilder:
|
|
|
430
431
|
self.currentTargetReport["rels"] = self.getRelationships(report)
|
|
431
432
|
|
|
432
433
|
docSetFiles = None
|
|
433
|
-
report.info(
|
|
434
|
+
report.info(INFO_MESSAGE_CODE, "Creating iXBRL viewer (%d of %d)" % (n+1, len(self.reports)))
|
|
434
435
|
if report.modelDocument.type == Type.INLINEXBRLDOCUMENTSET:
|
|
435
436
|
# Sort by object index to preserve order in which files were specified.
|
|
436
437
|
xmlDocsByFilename = {
|
|
@@ -535,7 +536,7 @@ class iXBRLViewer:
|
|
|
535
536
|
def addFilingDoc(self, filingDocuments):
|
|
536
537
|
self.filingDocuments = filingDocuments
|
|
537
538
|
|
|
538
|
-
def save(self, destination: io.BytesIO | str, zipOutput: bool = False, copyScriptPath:
|
|
539
|
+
def save(self, destination: io.BytesIO | str, zipOutput: bool = False, copyScriptPath: Path | None = None):
|
|
539
540
|
"""
|
|
540
541
|
Save the iXBRL viewer.
|
|
541
542
|
:param destination: The target that viewer data/files will be written to (path to file/directory, or a file object itself).
|
|
@@ -553,15 +554,15 @@ class iXBRLViewer:
|
|
|
553
554
|
fileMode = 'w'
|
|
554
555
|
elif destination.endswith(os.sep):
|
|
555
556
|
# Looks like a directory, but isn't one
|
|
556
|
-
self.logger_model.error(
|
|
557
|
+
self.logger_model.error(ERROR_MESSAGE_CODE, "Directory %s does not exist" % destination)
|
|
557
558
|
return
|
|
558
559
|
elif not os.path.isdir(os.path.dirname(os.path.abspath(destination))):
|
|
559
560
|
# Directory part of filename doesn't exist
|
|
560
|
-
self.logger_model.error(
|
|
561
|
+
self.logger_model.error(ERROR_MESSAGE_CODE, "Directory %s does not exist" % os.path.dirname(os.path.abspath(destination)))
|
|
561
562
|
return
|
|
562
563
|
elif not destination.endswith('.zip'):
|
|
563
564
|
# File extension isn't a zip
|
|
564
|
-
self.logger_model.error(
|
|
565
|
+
self.logger_model.error(ERROR_MESSAGE_CODE, "File extension %s is not a zip" % os.path.splitext(destination)[0])
|
|
565
566
|
return
|
|
566
567
|
else:
|
|
567
568
|
file = destination
|
|
@@ -569,57 +570,56 @@ class iXBRLViewer:
|
|
|
569
570
|
|
|
570
571
|
with zipfile.ZipFile(file, fileMode, zipfile.ZIP_DEFLATED, True) as zout:
|
|
571
572
|
for f in self.files:
|
|
572
|
-
self.logger_model.info(
|
|
573
|
+
self.logger_model.info(INFO_MESSAGE_CODE, "Saving in output zip %s" % f.filename)
|
|
573
574
|
with zout.open(f.filename, "w") as fout:
|
|
574
575
|
writer = XHTMLSerializer(fout)
|
|
575
576
|
writer.serialize(f.xmlDocument)
|
|
576
577
|
if self.filingDocuments:
|
|
577
578
|
filename = os.path.basename(self.filingDocuments)
|
|
578
|
-
self.logger_model.info(
|
|
579
|
+
self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % filename)
|
|
579
580
|
zout.write(self.filingDocuments, filename)
|
|
580
581
|
if copyScriptPath is not None:
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
zout.write(scriptSrc, os.path.basename(copyScriptPath))
|
|
582
|
+
self.logger_model.info(INFO_MESSAGE_CODE, f"Writing script from {copyScriptPath}")
|
|
583
|
+
zout.write(copyScriptPath, copyScriptPath.name)
|
|
584
584
|
elif os.path.isdir(destination):
|
|
585
585
|
# If output is a directory, write each file in the doc set to that
|
|
586
586
|
# directory using its existing filename
|
|
587
587
|
for f in self.files:
|
|
588
588
|
filename = os.path.join(destination, f.filename)
|
|
589
|
-
self.logger_model.info(
|
|
589
|
+
self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % filename)
|
|
590
590
|
with open(filename, "wb") as fout:
|
|
591
591
|
writer = XHTMLSerializer(fout)
|
|
592
592
|
writer.serialize(f.xmlDocument)
|
|
593
593
|
if self.filingDocuments:
|
|
594
594
|
filename = os.path.basename(self.filingDocuments)
|
|
595
|
-
self.logger_model.info(
|
|
595
|
+
self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % filename)
|
|
596
596
|
shutil.copy2(self.filingDocuments, os.path.join(destination, filename))
|
|
597
597
|
if copyScriptPath is not None:
|
|
598
|
-
self._copyScript(destination, copyScriptPath)
|
|
598
|
+
self._copyScript(Path(destination), copyScriptPath)
|
|
599
599
|
else:
|
|
600
600
|
if len(self.files) > 1:
|
|
601
|
-
self.logger_model.error(
|
|
601
|
+
self.logger_model.error(ERROR_MESSAGE_CODE, "More than one file in input, but output is not a directory")
|
|
602
602
|
elif destination.endswith(os.sep):
|
|
603
603
|
# Looks like a directory, but isn't one
|
|
604
|
-
self.logger_model.error(
|
|
604
|
+
self.logger_model.error(ERROR_MESSAGE_CODE, "Directory %s does not exist" % destination)
|
|
605
605
|
elif not os.path.isdir(os.path.dirname(os.path.abspath(destination))):
|
|
606
606
|
# Directory part of filename doesn't exist
|
|
607
|
-
self.logger_model.error(
|
|
607
|
+
self.logger_model.error(ERROR_MESSAGE_CODE, "Directory %s does not exist" % os.path.dirname(os.path.abspath(destination)))
|
|
608
608
|
else:
|
|
609
|
-
self.logger_model.info(
|
|
609
|
+
self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % destination)
|
|
610
610
|
with open(destination, "wb") as fout:
|
|
611
611
|
writer = XHTMLSerializer(fout)
|
|
612
612
|
writer.serialize(self.files[0].xmlDocument)
|
|
613
613
|
if self.filingDocuments:
|
|
614
614
|
filename = os.path.basename(self.filingDocuments)
|
|
615
|
-
self.logger_model.info(
|
|
615
|
+
self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % filename)
|
|
616
616
|
shutil.copy2(self.filingDocuments, os.path.join(os.path.dirname(destination), filename))
|
|
617
617
|
if copyScriptPath is not None:
|
|
618
|
-
outDirectory =
|
|
618
|
+
outDirectory = Path(destination).parent
|
|
619
619
|
self._copyScript(outDirectory, copyScriptPath)
|
|
620
620
|
|
|
621
|
-
def _copyScript(self,
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
621
|
+
def _copyScript(self, destDirectory: Path, scriptPath: Path):
|
|
622
|
+
scriptDest = destDirectory / scriptPath.name
|
|
623
|
+
if scriptPath != scriptDest:
|
|
624
|
+
self.logger_model.info(INFO_MESSAGE_CODE, f"Copying script from {scriptDest} to {scriptDest}.")
|
|
625
|
+
shutil.copy2(scriptPath, scriptDest)
|
iXBRLViewerPlugin/ui.py
CHANGED
|
@@ -8,9 +8,9 @@ except ImportError:
|
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
10
|
|
|
11
|
-
from .constants import CONFIG_FEATURE_PREFIX, CONFIG_FILE_DIRECTORY,
|
|
12
|
-
CONFIG_OUTPUT_FILE, CONFIG_SCRIPT_URL, CONFIG_ZIP_OUTPUT,
|
|
13
|
-
GUI_FEATURE_CONFIGS
|
|
11
|
+
from .constants import CONFIG_COPY_SCRIPT, CONFIG_FEATURE_PREFIX, CONFIG_FILE_DIRECTORY, \
|
|
12
|
+
CONFIG_LAUNCH_ON_LOAD, CONFIG_OUTPUT_FILE, CONFIG_SCRIPT_URL, CONFIG_ZIP_OUTPUT, \
|
|
13
|
+
DEFAULT_COPY_SCRIPT, DEFAULT_LAUNCH_ON_LOAD, GUI_FEATURE_CONFIGS
|
|
14
14
|
|
|
15
15
|
UNSET_SCRIPT_URL = ''
|
|
16
16
|
|
|
@@ -30,6 +30,8 @@ class BaseViewerDialog(Toplevel):
|
|
|
30
30
|
self._features[featureConfig.key] = featureVar
|
|
31
31
|
self._scriptUrl = StringVar()
|
|
32
32
|
self._scriptUrl.set(self.cntlr.config.setdefault(CONFIG_SCRIPT_URL, UNSET_SCRIPT_URL))
|
|
33
|
+
self._copyScript = BooleanVar()
|
|
34
|
+
self._copyScript.set(self.cntlr.config.setdefault(CONFIG_COPY_SCRIPT, DEFAULT_COPY_SCRIPT))
|
|
33
35
|
|
|
34
36
|
def addButtons(self, frame: Frame, x: int, y: int) -> int:
|
|
35
37
|
"""
|
|
@@ -62,6 +64,12 @@ class BaseViewerDialog(Toplevel):
|
|
|
62
64
|
scriptUrlLabel.grid(row=y, column=0, sticky=W, pady=3, padx=3)
|
|
63
65
|
scriptUrlEntry.grid(row=y, column=1, columnspan=2, sticky=EW, pady=3, padx=3)
|
|
64
66
|
|
|
67
|
+
y += 1
|
|
68
|
+
copyScriptCheckbutton = Checkbutton(frame, text="Copy Script", variable=self._copyScript, onvalue=True, offvalue=False)
|
|
69
|
+
copyScriptLabel = Label(frame, text="Copy the iXBRL Viewer script into the output directory.")
|
|
70
|
+
copyScriptCheckbutton.grid(row=y, column=0, pady=3, padx=3, sticky=W)
|
|
71
|
+
copyScriptLabel.grid(row=y, column=1, columnspan=3, pady=3, padx=3, sticky=W)
|
|
72
|
+
|
|
65
73
|
y += 1
|
|
66
74
|
featuresLabel = Label(frame, text="Generate with optional features:")
|
|
67
75
|
featuresLabel.grid(row=y, column=0, columnspan=2, pady=3, padx=3, sticky=W)
|
|
@@ -130,6 +138,9 @@ class BaseViewerDialog(Toplevel):
|
|
|
130
138
|
self.grab_set()
|
|
131
139
|
self.wait_window(self)
|
|
132
140
|
|
|
141
|
+
def copyScript(self):
|
|
142
|
+
return self._copyScript.get()
|
|
143
|
+
|
|
133
144
|
def features(self):
|
|
134
145
|
# Return list of feature keys with corresponding BooleanVar is set to True
|
|
135
146
|
return [feature for feature, value in self._features.items() if value.get()]
|
|
@@ -238,6 +249,7 @@ class SettingsDialog(BaseViewerDialog):
|
|
|
238
249
|
"""
|
|
239
250
|
self.cntlr.config[CONFIG_LAUNCH_ON_LOAD] = self._launchOnLoad.get()
|
|
240
251
|
self.cntlr.config[CONFIG_SCRIPT_URL] = self._scriptUrl.get()
|
|
252
|
+
self.cntlr.config[CONFIG_COPY_SCRIPT] = self._copyScript.get()
|
|
241
253
|
for key, var in self._features.items():
|
|
242
254
|
self.cntlr.config[f'{CONFIG_FEATURE_PREFIX}{key}'] = var.get()
|
|
243
255
|
self.cntlr.saveConfig()
|
|
@@ -249,5 +261,6 @@ class SettingsDialog(BaseViewerDialog):
|
|
|
249
261
|
"""
|
|
250
262
|
self._launchOnLoad.set(DEFAULT_LAUNCH_ON_LOAD)
|
|
251
263
|
self._scriptUrl.set(UNSET_SCRIPT_URL)
|
|
264
|
+
self._copyScript.set(DEFAULT_COPY_SCRIPT)
|
|
252
265
|
for featureConfig in GUI_FEATURE_CONFIGS:
|
|
253
266
|
self._features[featureConfig.key].set(featureConfig.guiDefault)
|