@@ -63,8 +63,15 @@ class NamespaceMap:
6363 """
6464
6565 def __init__ (self ) -> None :
66- self .nsmap : dict [str , str ] = {}
67- self .prefixmap : dict [str , str ] = {}
66+ self ._nsmap : dict [str , str ] = {}
67+ self ._reportNsMap : dict [str , str ] = {}
68+
69+ @property
70+ def prefixmap (self ) -> dict [str , str ]:
71+ """
72+ Get the current prefix map (prefix to namespace URI).
73+ """
74+ return {p : ns for ns , p in self ._nsmap .items ()}
6875
6976 def getPrefix (self , ns : str , preferredPrefix : str | None = None ) -> str :
7077 """
@@ -75,27 +82,46 @@ def getPrefix(self, ns: str, preferredPrefix: str | None = None) -> str:
7582 prefix (or the string "ns")
7683 """
7784
78- prefix = self .nsmap .get (ns , None )
79- if not prefix :
80- if preferredPrefix and preferredPrefix not in self .prefixmap :
81- prefix = preferredPrefix
82- else :
83- p = preferredPrefix if preferredPrefix else "ns"
84- n = 0
85- while f"{ p } { n } " in self .prefixmap :
86- n += 1
85+ if (boundPrefix := self ._nsmap .get (ns , None )) is not None :
86+ return boundPrefix
8787
88- prefix = f"{ p } { n } "
88+ if preferredPrefix is None :
89+ # For example, EE2.0 fact values (expanded names) get turned in to
90+ # QNames without a prefix but the report will know the prefix
91+ preferredPrefix = self ._reportNsMap .get (ns , None )
92+
93+ all_bound_prefixes = self ._nsmap .values ()
94+
95+ if preferredPrefix and preferredPrefix not in all_bound_prefixes :
96+ chosenPrefix = preferredPrefix
97+ else :
98+ p = preferredPrefix if preferredPrefix else "ns"
99+ n = 0
100+ while (chosenPrefix := f"{ p } { n } " ) in all_bound_prefixes :
101+ n += 1
89102
90- self .prefixmap [prefix ] = ns
91- self .nsmap [ns ] = prefix
92- return prefix
103+ self ._nsmap [ns ] = chosenPrefix
104+ return chosenPrefix
93105
94106 def qname (self , qname : QName ) -> str :
95107 if qname .namespaceURI is None :
96108 return qname .localName
97109 return f"{ self .getPrefix (qname .namespaceURI , qname .prefix )} :{ qname .localName } "
98110
111+ def clear (self ) -> None :
112+ """
113+ Clear the namespace map.
114+ """
115+ self ._nsmap .clear ()
116+ self ._reportNsMap .clear ()
117+
118+ def stashReportNSMap (self , report : ModelXbrl ) -> None :
119+ """
120+ Stash a copy of Arelle's prefix/namespace map in case it can be used
121+ later to find a preferred prefix.
122+ """
123+ self ._reportNsMap = {ns : p for p , ns in report .prefixedNamespaces .items ()}
124+
99125
100126class IXBRLViewerBuilderError (Exception ):
101127 pass
@@ -501,6 +527,7 @@ def addSourceReport(self) -> dict[str, list[Any]]:
501527 return sourceReport
502528
503529 def processModel (self , report : ModelXbrl ) -> None :
530+ self .nsmap .stashReportNSMap (report )
504531 self .footnoteRelationshipSet = ModelRelationshipSet (report , "XBRL-footnotes" ) # type: ignore[no-untyped-call]
505532 self .currentTargetReport = self .newTargetReport (getattr (report , "ixdsTarget" , None ))
506533 softwareCredits = set ()
@@ -600,7 +627,7 @@ def createViewer(
600627 scriptUrl : str = DEFAULT_JS_FILENAME ,
601628 showValidations : bool = True ,
602629 packageDownloadURL : str | None = None ,
603- ) -> ' iXBRLViewer' | None :
630+ ) -> iXBRLViewer | None :
604631 """
605632 Create an iXBRL file with XBRL data as a JSON blob, and script tags added.
606633 :param scriptUrl: The `src` value of the script tag that loads the viewer script.
0 commit comments