Skip to content

Commit

Permalink
Merge pull request #611 from austinmatherne-wk/XT-3532
Browse files Browse the repository at this point in the history
  • Loading branch information
austinmatherne-wk authored Jan 9, 2024
2 parents ab45cac + ac306c6 commit 6d38060
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/node-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
- ubuntu-22.04
- windows-2022
node-version:
- '16'
- '18'
- '20'
- '21'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/[email protected]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/puppeteer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
- ubuntu-22.04
- windows-2022
node-version:
- '16'
- '18'
- '20'
- '21'
python-version:
- '3.12'
runs-on: ${{ matrix.os }}
Expand Down
50 changes: 25 additions & 25 deletions iXBRLViewerPlugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import tempfile
import traceback
from optparse import OptionGroup, OptionParser
from typing import Optional, Union

from arelle import Cntlr
from arelle.LocalViewer import LocalViewer
Expand All @@ -18,7 +17,7 @@

from .constants import CONFIG_FEATURE_PREFIX, CONFIG_LAUNCH_ON_LOAD, \
CONFIG_SCRIPT_URL, DEFAULT_LAUNCH_ON_LOAD, DEFAULT_OUTPUT_NAME, \
DEFAULT_VIEWER_PATH, FEATURE_CONFIGS
DEFAULT_JS_FILENAME, DEFAULT_VIEWER_PATH, FEATURE_CONFIGS
from .iXBRLViewer import IXBRLViewerBuilder, IXBRLViewerBuilderError


Expand Down Expand Up @@ -96,13 +95,14 @@ def iXBRLViewerCommandLineOptionExtender(parser, *args, **kwargs):

def generateViewer(
cntlr: Cntlr,
saveViewerDest: Union[io.BytesIO, str],
saveViewerDest: io.BytesIO | str | None,
viewerURL: str = DEFAULT_VIEWER_PATH,
showValidationMessages: bool = False,
useStubViewer: bool = False,
zipViewerOutput: bool = False,
features: Optional[list[str]] = None,
packageDownloadURL: str = None):
features: list[str] | None = None,
packageDownloadURL: str | None = None,
) -> None:
"""
Generate and save a viewer at the given destination (file, directory, or in-memory file) with the given viewer URL.
If the viewer URL is a location on the local file system, a copy will be placed included in the output destination.
Expand All @@ -115,6 +115,8 @@ def generateViewer(
:param features: List of feature names to enable via generated JSON data.
"""
# extend XBRL-loaded run processing for this option
if saveViewerDest is None:
return
if (cntlr.modelManager is None
or len(cntlr.modelManager.loadedModelXbrls) == 0
or any(not mx.modelDocument for mx in cntlr.modelManager.loadedModelXbrls)):
Expand Down Expand Up @@ -142,17 +144,15 @@ def generateViewer(
copyScriptPath = viewerURL
viewerURL = os.path.basename(viewerURL)
try:
out = saveViewerDest
if out:
viewerBuilder = IXBRLViewerBuilder(cntlr.modelManager.loadedModelXbrls)
if features:
for feature in features:
viewerBuilder.enableFeature(feature)
iv = viewerBuilder.createViewer(scriptUrl=viewerURL, showValidations=showValidationMessages, useStubViewer=useStubViewer, packageDownloadURL=packageDownloadURL)
if iv is not None:
iv.save(out, zipOutput=zipViewerOutput, copyScriptPath=copyScriptPath)
viewerBuilder = IXBRLViewerBuilder(cntlr.modelManager.loadedModelXbrls)
if features:
for feature in features:
viewerBuilder.enableFeature(feature)
iv = viewerBuilder.createViewer(scriptUrl=viewerURL, showValidations=showValidationMessages, useStubViewer=useStubViewer, packageDownloadURL=packageDownloadURL)
if iv is not None:
iv.save(saveViewerDest, zipOutput=zipViewerOutput, copyScriptPath=copyScriptPath)
except IXBRLViewerBuilderError as ex:
print(ex.message)
print(ex)
except Exception as ex:
cntlr.addToLog("Exception {} \nTraceback {}".format(ex, traceback.format_tb(sys.exc_info()[2])))

Expand All @@ -170,7 +170,7 @@ def getAbsoluteViewerPath(saveViewerPath: str, relativeViewerPath: str) -> str:
return os.path.join(saveViewerDir, relativeViewerPath)


def getFeaturesFromOptions(options: Union[argparse.Namespace, OptionParser]):
def getFeaturesFromOptions(options: argparse.Namespace | OptionParser):
return [
featureConfig.key
for featureConfig in FEATURE_CONFIGS
Expand Down Expand Up @@ -206,7 +206,7 @@ def iXBRLViewerSaveCommand(cntlr):
generateViewer(
cntlr,
dialog.filename(),
dialog.scriptUrl(),
dialog.scriptUrl() or DEFAULT_VIEWER_PATH,
zipViewerOutput=dialog.zipViewerOutput(),
features=dialog.features()
)
Expand Down Expand Up @@ -249,23 +249,23 @@ def commandLineRun(*args, **kwargs):
class iXBRLViewerLocalViewer(LocalViewer):
# plugin-specific local file handler
def getLocalFile(self, file, relpath, request):
_report, _sep, _file = file.partition("/")
if file == 'ixbrlviewer.js':
return static_file('ixbrlviewer.js', os.path.dirname(DEFAULT_VIEWER_PATH))
elif _report.isnumeric(): # in reportsFolder folder
if file == DEFAULT_JS_FILENAME:
return static_file(DEFAULT_JS_FILENAME, os.path.dirname(DEFAULT_VIEWER_PATH))
_report, _, _file = file.partition("/")
if _report.isnumeric(): # in reportsFolder folder
# check if file is in the current or parent directory
_fileDir = self.reportsFolders[int(_report)]
_fileExists = False
if os.path.exists(os.path.join(_fileDir, _file)):
_fileExists = True
elif "/" in _file and os.path.exists(os.path.join(_fileDir, os.path.filepart(_file))):
elif "/" in _file and os.path.exists(os.path.join(_fileDir, os.path.basename(_file))):
# xhtml in a subdirectory for output files may refer to an image file in parent directory
_fileExists = True
_file = os.path.filepart(_file)
_file = os.path.basename(_file)
if not _fileExists:
self.cntlr.addToLog("http://localhost:{}/{}".format(self.port, file), messageCode="localViewer:fileNotFound", level=logging.DEBUG)
return static_file(_file, root=_fileDir, headers=self.noCacheHeaders) # extra_headers modification to py-bottle
return static_file(file, root="/") # probably can't get here unless path is wrong
return static_file(file, root="/") # absolute path used for ixbrlviewer.js.


def guiRun(cntlr, modelXbrl, attach, *args, **kwargs):
Expand All @@ -286,7 +286,7 @@ def guiRun(cntlr, modelXbrl, attach, *args, **kwargs):
generateViewer(
cntlr,
saveViewerDest=tempViewer.name,
viewerURL=cntlr.config.setdefault(CONFIG_SCRIPT_URL, DEFAULT_VIEWER_PATH),
viewerURL=cntlr.config.get(CONFIG_SCRIPT_URL) or DEFAULT_VIEWER_PATH,
useStubViewer=True,
features=features
)
Expand Down
3 changes: 2 additions & 1 deletion iXBRLViewerPlugin/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

DEFAULT_LAUNCH_ON_LOAD = True
DEFAULT_OUTPUT_NAME = 'ixbrlviewer.html'
DEFAULT_VIEWER_PATH = os.path.join(os.path.dirname(__file__), "viewer", "dist", "ixbrlviewer.js")
DEFAULT_JS_FILENAME = 'ixbrlviewer.js'
DEFAULT_VIEWER_PATH = os.path.join(os.path.dirname(__file__), "viewer", "dist", DEFAULT_JS_FILENAME)

FEATURE_CONFIGS = [
FeatureConfig(
Expand Down
11 changes: 8 additions & 3 deletions iXBRLViewerPlugin/iXBRLViewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import zipfile
from collections import defaultdict
from copy import deepcopy
from typing import Optional, Union

import pycountry
from arelle import XbrlConst
Expand Down Expand Up @@ -390,7 +389,13 @@ def addSourceReport(self):
self.taxonomyData["sourceReports"].append(sourceReport)
return sourceReport

def createViewer(self, scriptUrl: str = DEFAULT_VIEWER_PATH, useStubViewer: bool = False, showValidations: bool = True, packageDownloadURL: str = None) -> Optional[iXBRLViewer]:
def createViewer(
self,
scriptUrl: str = DEFAULT_VIEWER_PATH,
useStubViewer: bool = False,
showValidations: bool = True,
packageDownloadURL: str | None = None,
) -> iXBRLViewer | None:
"""
Create an iXBRL file with XBRL data as a JSON blob, and script tags added.
:param scriptUrl: The `src` value of the script tag that loads the viewer script.
Expand Down Expand Up @@ -526,7 +531,7 @@ def addFile(self, ivf):
def addFilingDoc(self, filingDocuments):
self.filingDocuments = filingDocuments

def save(self, destination: Union[io.BytesIO, str], zipOutput: bool = False, copyScriptPath: Optional[str] = None):
def save(self, destination: io.BytesIO | str, zipOutput: bool = False, copyScriptPath: str | None = None):
"""
Save the iXBRL viewer.
:param destination: The target that viewer data/files will be written to (path to file/directory, or a file object itself).
Expand Down
9 changes: 5 additions & 4 deletions iXBRLViewerPlugin/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

from .constants import CONFIG_FEATURE_PREFIX, CONFIG_FILE_DIRECTORY, CONFIG_LAUNCH_ON_LOAD, \
CONFIG_OUTPUT_FILE, CONFIG_SCRIPT_URL, CONFIG_ZIP_OUTPUT, DEFAULT_LAUNCH_ON_LOAD, \
DEFAULT_VIEWER_PATH, GUI_FEATURE_CONFIGS
GUI_FEATURE_CONFIGS

UNSET_SCRIPT_URL = ''

class BaseViewerDialog(Toplevel):
"""
Expand All @@ -28,7 +29,7 @@ def __init__(self, cntlr):
featureVar.set(self.cntlr.config.setdefault(f'{CONFIG_FEATURE_PREFIX}{featureConfig.key}', featureConfig.guiDefault))
self._features[featureConfig.key] = featureVar
self._scriptUrl = StringVar()
self._scriptUrl.set(self.cntlr.config.setdefault(CONFIG_SCRIPT_URL, DEFAULT_VIEWER_PATH))
self._scriptUrl.set(self.cntlr.config.setdefault(CONFIG_SCRIPT_URL, UNSET_SCRIPT_URL))

def addButtons(self, frame: Frame, x: int, y: int) -> int:
"""
Expand Down Expand Up @@ -56,7 +57,7 @@ def addFields(self, frame: Frame, y: int) -> int:
:return: Row `y` that the last field was added on
"""
y += 1
scriptUrlLabel = Label(frame, text="Script URL")
scriptUrlLabel = Label(frame, text="Script URL (leave blank for default)")
scriptUrlEntry = Entry(frame, textvariable=self._scriptUrl, width=80)
scriptUrlLabel.grid(row=y, column=0, sticky=W, pady=3, padx=3)
scriptUrlEntry.grid(row=y, column=1, columnspan=2, sticky=EW, pady=3, padx=3)
Expand Down Expand Up @@ -247,6 +248,6 @@ def reset(self, event=None):
Resets dialog variable values to default values
"""
self._launchOnLoad.set(DEFAULT_LAUNCH_ON_LOAD)
self._scriptUrl.set(DEFAULT_VIEWER_PATH)
self._scriptUrl.set(UNSET_SCRIPT_URL)
for featureConfig in GUI_FEATURE_CONFIGS:
self._features[featureConfig.key].set(featureConfig.guiDefault)
14 changes: 6 additions & 8 deletions iXBRLViewerPlugin/viewer/src/js/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,12 @@ export class Viewer {
/* Otherwise, insert a <span> as wrapper */
if (nodes.length == 0) {
nodes.push(this._wrapNode(domNode));

// Create a list of the wrapper node, and all absolutely positioned
// descendants.
for (const e of domNode.querySelectorAll("*")) {
if (getComputedStyle(e).getPropertyValue('position') === "absolute") {
nodes.push(e);
}
}
}
// Create a list of the wrapper node, and all absolutely positioned descendants.
for (const e of nodes[0].querySelectorAll("*")) {
if (getComputedStyle(e).getPropertyValue('position') === "absolute") {
nodes.push(e);
}
}
for (const [i, n] of nodes.entries()) {
// getBoundingClientRect blocks on layout, so only do it if we've actually got absolute nodes
Expand Down
6 changes: 3 additions & 3 deletions iXBRLViewerPlugin/viewer/src/less/common.less
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

/* Styles common to both inspector and highlighting used within iframe */
.linked-highlight-text {
outline: dashed 2px @linked-fact;
outline-offset: 1px;
outline: dashed 0.125em @linked-fact;
outline-offset: 0.0625em;
}

.linked-highlight-cell {
.linked-highlight-text();

outline-offset: -1px;
outline-offset: -0.0625em;
}
4 changes: 2 additions & 2 deletions iXBRLViewerPlugin/viewer/src/less/inspector.less
Original file line number Diff line number Diff line change
Expand Up @@ -1064,8 +1064,8 @@
color: @primary;

&:hover {
outline: solid 2px @linked-fact;
outline-offset: 1px;
outline: solid 0.125em @linked-fact;
outline-offset: 0.0625em;
}
}

Expand Down
24 changes: 12 additions & 12 deletions iXBRLViewerPlugin/viewer/src/less/viewer.less
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ div,
span {
&:not(.ixbrl-no-highlight) {
&.ixbrl-selected {
outline: solid 2px @primary-focus;
outline-offset: 1px;
outline: solid 0.125em @primary-focus;
outline-offset: 0.0625em;
}

&.ixbrl-element,
&.ixbrl-sub-element {
&:hover:not(.ixbrl-selected) {
outline: dashed 2px @primary-focus;
outline-offset: 1px;
outline: dashed 0.125em @primary-focus;
outline-offset: 0.0625em;
}
}
}
Expand All @@ -65,10 +65,10 @@ span {
cursor: pointer;

&.ixbrl-related {
outline: dashed 2px @related-fact;
outline: dashed 0.125em @related-fact;

&:not(td, th) {
outline-offset: 1px;
outline-offset: 0.0625em;
}
}
}
Expand All @@ -78,19 +78,19 @@ td,
th {
&:not(.ixbrl-no-highlight) {
&.ixbrl-selected {
outline: solid 2px @primary-focus !important;
outline-offset: -1px !important;
outline: solid 0.125em @primary-focus !important;
outline-offset: -0.0625em !important;
}

&.ixbrl-element:hover:not(.ixbrl-selected),
&.ixbrl-sub-element:hover:not(.ixbrl-selected) {
outline: dashed 2px @primary-focus !important;
outline-offset: -1px !important;
outline: dashed 0.125em @primary-focus !important;
outline-offset: -0.0625em !important;
}
}

&.ixbrl-related {
outline-offset: -1px;
outline-offset: -0.0625em;
}
}

Expand All @@ -103,7 +103,7 @@ td.ixbrl-related.ixbrl-linked-highlight:not(.ixbrl-no-highlight),
td.ixbrl-linked-highlight:not(.ixbrl-no-highlight) {
.linked-highlight-cell();

outline-offset: -1px;
outline-offset: -0.0625em;
}

div.ixbrl-table-handle {
Expand Down
2 changes: 1 addition & 1 deletion samples/build-viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def createViewer(self, f, scriptUrl=None, outPath=None, useStubViewer=False, fea
try:
generateViewer(self, outPath, scriptUrl, showValidationMessages=True, useStubViewer=useStubViewer, features=features)
except iXBRLViewerPlugin.iXBRLViewer.IXBRLViewerBuilderError as e:
print(e.message)
print(e)
sys.exit(1)

parser = argparse.ArgumentParser(description="Create iXBRL Viewer instances")
Expand Down

0 comments on commit 6d38060

Please sign in to comment.