def createUnit(self, multiplyBy, divideBy, afterSibling=None, beforeSibling=None): """Creates new unit, by measures, as in formula usage, if any :param multiplyBy: List of multiply-by measure QNames (or top level measures if no divideBy) :type multiplyBy: [QName] :param divideBy: List of multiply-by measure QNames (or empty list if no divideBy) :type divideBy: [QName] :param beforeSibling: lxml element in instance to insert new concept before :type beforeSibling: ModelObject :param afterSibling: lxml element in instance to insert new concept after :type afterSibling: ModelObject :returns: ModelUnit -- New unit object """ xbrlElt = self.modelDocument.xmlRootElement if afterSibling == AUTO_LOCATE_ELEMENT: afterSibling = XmlUtil.lastChild(xbrlElt, XbrlConst.xbrli, ("schemaLocation", "roleType", "arcroleType", "context", "unit")) unitId = 'u-{0:02n}'.format( len(self.units) + 1) newUnitElt = XmlUtil.addChild(xbrlElt, XbrlConst.xbrli, "unit", attributes=("id", unitId), afterSibling=afterSibling, beforeSibling=beforeSibling) if len(divideBy) == 0: for multiply in multiplyBy: XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, multiply)) else: divElt = XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "divide") numElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitNumerator") denElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitDenominator") for multiply in multiplyBy: XmlUtil.addChild(numElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, multiply)) for divide in divideBy: XmlUtil.addChild(denElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, divide)) self.modelDocument.unitDiscover(newUnitElt) XmlValidate.validate(self, newUnitElt) return newUnitElt
def generateAxis(newLinkElt, newAxisParentElt, srcAxisElt, axisMbrRelSet, visited): for rel in axisMbrRelSet.fromModelObject(srcAxisElt): tgtAxisElt = rel.toModelObject if isinstance(tgtAxisElt, ModelEuAxisCoord) and tgtAxisElt not in visited: visited.add(tgtAxisElt) newAxisElt = etree.SubElement(newLinkElt, "{http://xbrl.org/2011/table}ruleAxis") copyAttrs(tgtAxisElt, newAxisElt, ("id", "abstract", "{http://www.w3.org/1999/xlink}type", "{http://www.w3.org/1999/xlink}label")) if tgtAxisElt.primaryItemQname: newRuleElt = etree.SubElement(newAxisElt, "{http://xbrl.org/2008/formula}concept") newQnameElt = etree.SubElement(newRuleElt, "{http://xbrl.org/2008/formula}qname") newQnameElt.text = XmlUtil.addQnameValue(docObj, tgtAxisElt.primaryItemQname) for dimQname, memQname in tgtAxisElt.explicitDims: newRuleElt = etree.SubElement(newAxisElt, "{http://xbrl.org/2008/formula}explicitDimension") newRuleElt.set("dimension", XmlUtil.addQnameValue(docObj, dimQname)) newMbrElt = etree.SubElement(newRuleElt, "{http://xbrl.org/2008/formula}member") newQnameElt = etree.SubElement(newMbrElt, "{http://xbrl.org/2008/formula}qname") newQnameElt.text = XmlUtil.addQnameValue(docObj, memQname) newArcElt = etree.SubElement(newLinkElt, "{http://xbrl.org/2011/table}axisArc") copyAttrs(rel, newArcElt, ("id", "{http://www.w3.org/1999/xlink}type", "{http://www.w3.org/1999/xlink}from", "{http://www.w3.org/1999/xlink}to", "order")) newArcElt.set("{http://www.w3.org/1999/xlink}arcrole", XbrlConst.tableAxisSubtree) generateAxis(newLinkElt, newAxisElt, tgtAxisElt, axisMbrRelSet, visited) visited.discard(tgtAxisElt)
def generateAxis(newLinkElt, newAxisParentElt, srcAxisElt, axisMbrRelSet, visited): for rel in axisMbrRelSet.fromModelObject(srcAxisElt): tgtAxisElt = rel.toModelObject if isinstance(tgtAxisElt, ModelEuAxisCoord) and tgtAxisElt not in visited: visited.add(tgtAxisElt) newAxisElt = etree.SubElement( newLinkElt, "{http://xbrl.org/2011/table}ruleAxis") copyAttrs( tgtAxisElt, newAxisElt, ("id", "abstract", "{http://www.w3.org/1999/xlink}type", "{http://www.w3.org/1999/xlink}label")) if tgtAxisElt.primaryItemQname: newRuleElt = etree.SubElement( newAxisElt, "{http://xbrl.org/2008/formula}concept") newQnameElt = etree.SubElement( newRuleElt, "{http://xbrl.org/2008/formula}qname") newQnameElt.text = XmlUtil.addQnameValue( docObj, tgtAxisElt.primaryItemQname) for dimQname, memQname in tgtAxisElt.explicitDims: newRuleElt = etree.SubElement( newAxisElt, "{http://xbrl.org/2008/formula}explicitDimension") newRuleElt.set("dimension", XmlUtil.addQnameValue(docObj, dimQname)) newMbrElt = etree.SubElement( newRuleElt, "{http://xbrl.org/2008/formula}member") newQnameElt = etree.SubElement( newMbrElt, "{http://xbrl.org/2008/formula}qname") newQnameElt.text = XmlUtil.addQnameValue(docObj, memQname) newArcElt = etree.SubElement( newLinkElt, "{http://xbrl.org/2011/table}axisArc") copyAttrs(rel, newArcElt, ("id", "{http://www.w3.org/1999/xlink}type", "{http://www.w3.org/1999/xlink}from", "{http://www.w3.org/1999/xlink}to", "order")) newArcElt.set("{http://www.w3.org/1999/xlink}arcrole", XbrlConst.tableAxisSubtree) generateAxis(newLinkElt, newAxisElt, tgtAxisElt, axisMbrRelSet, visited) visited.discard(tgtAxisElt)
def createUnit(self, multiplyBy, divideBy, afterSibling=None, beforeSibling=None): xbrlElt = self.modelDocument.xmlRootElement if afterSibling == AUTO_LOCATE_ELEMENT: afterSibling = XmlUtil.lastChild( xbrlElt, XbrlConst.xbrli, ("schemaLocation", "roleType", "arcroleType", "context", "unit")) unitId = 'u-{0:02n}'.format(len(self.units) + 1) newUnitElt = XmlUtil.addChild(xbrlElt, XbrlConst.xbrli, "unit", attributes=("id", unitId), afterSibling=afterSibling, beforeSibling=beforeSibling) if len(divideBy) == 0: for multiply in multiplyBy: XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, multiply)) else: divElt = XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "divide") numElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitNumerator") denElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitDenominator") for multiply in multiplyBy: XmlUtil.addChild(numElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, multiply)) for divide in divideBy: XmlUtil.addChild(denElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, divide)) self.modelDocument.unitDiscover(newUnitElt) XmlValidate.validate(self, newUnitElt) return newUnitElt
def createUnit(self, multiplyBy, divideBy, afterSibling=None, beforeSibling=None): xbrlElt = self.modelDocument.xmlRootElement if afterSibling == AUTO_LOCATE_ELEMENT: afterSibling = XmlUtil.lastChild(xbrlElt, XbrlConst.xbrli, ("schemaLocation", "roleType", "arcroleType", "context", "unit")) unitId = 'u-{0:02n}'.format( len(self.units) + 1) newUnitElt = XmlUtil.addChild(xbrlElt, XbrlConst.xbrli, "unit", attributes=("id", unitId), afterSibling=afterSibling, beforeSibling=beforeSibling) if len(divideBy) == 0: for multiply in multiplyBy: XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, multiply)) else: divElt = XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "divide") numElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitNumerator") denElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitDenominator") for multiply in multiplyBy: XmlUtil.addChild(numElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, multiply)) for divide in divideBy: XmlUtil.addChild(denElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, divide)) self.modelDocument.unitDiscover(newUnitElt) XmlValidate.validate(self, newUnitElt) return newUnitElt
def createContext(self, entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, priItem, dims, segOCCs, scenOCCs, afterSibling=None, beforeSibling=None): xbrlElt = self.modelDocument.xmlRootElement if afterSibling == AUTO_LOCATE_ELEMENT: afterSibling = XmlUtil.lastChild(xbrlElt, XbrlConst.xbrli, ("schemaLocation", "roleType", "arcroleType", "context")) cntxId = 'c-{0:02n}'.format( len(self.contexts) + 1) newCntxElt = XmlUtil.addChild(xbrlElt, XbrlConst.xbrli, "context", attributes=("id", cntxId), afterSibling=afterSibling, beforeSibling=beforeSibling) entityElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "entity") XmlUtil.addChild(entityElt, XbrlConst.xbrli, "identifier", attributes=("scheme", entityIdentScheme), text=entityIdentValue) periodElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "period") if periodType == "forever": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "forever") elif periodType == "instant": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "instant", text=XmlUtil.dateunionValue(periodEndInstant, subtractOneDay=True)) elif periodType == "duration": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "startDate", text=XmlUtil.dateunionValue(periodStart)) XmlUtil.addChild(periodElt, XbrlConst.xbrli, "endDate", text=XmlUtil.dateunionValue(periodEndInstant, subtractOneDay=True)) segmentElt = None scenarioElt = None from arelle.ModelInstanceObject import ModelDimensionValue if dims: # requires primary item to determin ambiguous concepts ''' in theory we have to check full set of dimensions for validity in source or any other context element, but for shortcut will see if each dimension is already reported in an unambiguous valid contextElement ''' from arelle.PrototypeInstanceObject import FactPrototype, ContextPrototype, DimValuePrototype fp = FactPrototype(self, priItem, dims.items()) # force trying a valid prototype's context Elements if not isFactDimensionallyValid(self, fp, setPrototypeContextElements=True): self.info("arelleLinfo", _("Create context for %(priItem)s, cannot determine valid context elements, no suitable hypercubes"), modelObject=self, priItem=priItem) fpDims = fp.context.qnameDims for dimQname in sorted(fpDims.keys()): dimValue = fpDims[dimQname] if isinstance(dimValue, DimValuePrototype): dimMemberQname = dimValue.memberQname # None if typed dimension contextEltName = dimValue.contextElement else: # qname for explicit or node for typed dimMemberQname = None contextEltName = None if contextEltName == "segment": if segmentElt is None: segmentElt = XmlUtil.addChild(entityElt, XbrlConst.xbrli, "segment") contextElt = segmentElt elif contextEltName == "scenario": if scenarioElt is None: scenarioElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "scenario") contextElt = scenarioElt else: self.info("arelleLinfo", _("Create context, %(dimension)s, cannot determine context element, either no all relationship or validation issue"), modelObject=self, dimension=dimQname), continue dimConcept = self.qnameConcepts[dimQname] dimAttr = ("dimension", XmlUtil.addQnameValue(xbrlElt, dimConcept.qname)) if dimConcept.isTypedDimension: dimElt = XmlUtil.addChild(contextElt, XbrlConst.xbrldi, "xbrldi:typedMember", attributes=dimAttr) if isinstance(dimValue, (ModelDimensionValue, DimValuePrototype)) and dimValue.isTyped: XmlUtil.copyNodes(dimElt, dimValue.typedMember) elif dimMemberQname: dimElt = XmlUtil.addChild(contextElt, XbrlConst.xbrldi, "xbrldi:explicitMember", attributes=dimAttr, text=XmlUtil.addQnameValue(xbrlElt, dimMemberQname)) if segOCCs: if segmentElt is None: segmentElt = XmlUtil.addChild(entityElt, XbrlConst.xbrli, "segment") XmlUtil.copyNodes(segmentElt, segOCCs) if scenOCCs: if scenarioElt is None: scenarioElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "scenario") XmlUtil.copyNodes(scenarioElt, scenOCCs) self.modelDocument.contextDiscover(newCntxElt) XmlValidate.validate(self, newCntxElt) return newCntxElt
def createContext(self, entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, priItem, dims, segOCCs, scenOCCs, afterSibling=None, beforeSibling=None): xbrlElt = self.modelDocument.xmlRootElement if afterSibling == AUTO_LOCATE_ELEMENT: afterSibling = XmlUtil.lastChild( xbrlElt, XbrlConst.xbrli, ("schemaLocation", "roleType", "arcroleType", "context")) cntxId = 'c-{0:02n}'.format(len(self.contexts) + 1) newCntxElt = XmlUtil.addChild(xbrlElt, XbrlConst.xbrli, "context", attributes=("id", cntxId), afterSibling=afterSibling, beforeSibling=beforeSibling) entityElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "entity") XmlUtil.addChild(entityElt, XbrlConst.xbrli, "identifier", attributes=("scheme", entityIdentScheme), text=entityIdentValue) periodElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "period") if periodType == "forever": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "forever") elif periodType == "instant": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "instant", text=XmlUtil.dateunionValue(periodEndInstant, subtractOneDay=True)) elif periodType == "duration": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "startDate", text=XmlUtil.dateunionValue(periodStart)) XmlUtil.addChild(periodElt, XbrlConst.xbrli, "endDate", text=XmlUtil.dateunionValue(periodEndInstant, subtractOneDay=True)) segmentElt = None scenarioElt = None from arelle.ModelInstanceObject import ModelDimensionValue if dims: # requires primary item to determin ambiguous concepts ''' in theory we have to check full set of dimensions for validity in source or any other context element, but for shortcut will see if each dimension is already reported in an unambiguous valid contextElement ''' from arelle.PrototypeInstanceObject import FactPrototype, ContextPrototype, DimValuePrototype fp = FactPrototype(self, priItem, dims.items()) # force trying a valid prototype's context Elements if not isFactDimensionallyValid( self, fp, setPrototypeContextElements=True): self.info( "arelleLinfo", _("Create context for %(priItem)s, cannot determine valid context elements, no suitable hypercubes" ), modelObject=self, priItem=priItem) fpDims = fp.context.qnameDims for dimQname in sorted(fpDims.keys()): dimValue = fpDims[dimQname] if isinstance(dimValue, DimValuePrototype): dimMemberQname = dimValue.memberQname # None if typed dimension contextEltName = dimValue.contextElement else: # qname for explicit or node for typed dimMemberQname = None contextEltName = None if contextEltName == "segment": if segmentElt is None: segmentElt = XmlUtil.addChild(entityElt, XbrlConst.xbrli, "segment") contextElt = segmentElt elif contextEltName == "scenario": if scenarioElt is None: scenarioElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "scenario") contextElt = scenarioElt else: self.info( "arelleLinfo", _("Create context, %(dimension)s, cannot determine context element, either no all relationship or validation issue" ), modelObject=self, dimension=dimQname), continue dimConcept = self.qnameConcepts[dimQname] dimAttr = ("dimension", XmlUtil.addQnameValue(xbrlElt, dimConcept.qname)) if dimConcept.isTypedDimension: dimElt = XmlUtil.addChild(contextElt, XbrlConst.xbrldi, "xbrldi:typedMember", attributes=dimAttr) if isinstance(dimValue, (ModelDimensionValue, DimValuePrototype)) and dimValue.isTyped: XmlUtil.copyNodes(dimElt, dimValue.typedMember) elif dimMemberQname: dimElt = XmlUtil.addChild(contextElt, XbrlConst.xbrldi, "xbrldi:explicitMember", attributes=dimAttr, text=XmlUtil.addQnameValue( xbrlElt, dimMemberQname)) if segOCCs: if segmentElt is None: segmentElt = XmlUtil.addChild(entityElt, XbrlConst.xbrli, "segment") XmlUtil.copyNodes(segmentElt, segOCCs) if scenOCCs: if scenarioElt is None: scenarioElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "scenario") XmlUtil.copyNodes(scenarioElt, scenOCCs) self.modelDocument.contextDiscover(newCntxElt) XmlValidate.validate(self, newCntxElt) return newCntxElt
def processQbResponse(qbRequest, responseXml): from arelle import ModelXbrl, XbrlConst from arelle.ModelValue import qname ticket = qbRequest["ticket"] qbRequestStatus[ticket] = _("Generating XBRL-GL from QuickBooks response") qbReport = qbRequest["request"] xbrlFile = qbRequest["xbrlFile"] fromDate = qbRequest["fromDate"] toDate = qbRequest["toDate"] strHCPResponse = qbRequest.get("strHCPResponse", "") # uncomment to dump out QB responses ''' with open("c:/temp/test.xml", "w") as fh: fh.write(responseXml) with open("c:/temp/testC.xml", "w") as fh: fh.write(strHCPResponse) # qb responses dump ''' companyQbDoc = etree.parse(io.StringIO(initial_value=strHCPResponse)) responseQbDoc = etree.parse(io.StringIO(initial_value=responseXml)) # columns table colTypeId = {} colIdType = {} for colDescElt in responseQbDoc.iter("ColDesc"): colTypeElt = colDescElt.find("ColType") if colTypeElt is not None: colID = colDescElt.get("colID") colType = colTypeElt.text if colType == "Amount": # check if there's a credit or debit colTitle for colTitleElt in colDescElt.iter("ColTitle"): title = colTitleElt.get("value") if title in ("Credit", "Debit"): colType = title break colTypeId[colType] = colID colIdType[colID] = colType # open new result instance document # load GL palette file (no instance) instance = cntlr.modelManager.load("http://www.xbrl.org/taxonomy/int/gl/2006-10-25/plt/case-c-b-m-u-t/gl-plt-2006-10-25.xsd") if xbrlFile is None: xbrlFile = "sampleInstance.xbrl" saveInstance = False else: saveInstance = True instance.createInstance(xbrlFile) # creates an instance as this modelXbrl's entrypoing newCntx = instance.createContext("http://www.xbrl.org/xbrlgl/sample", "SAMPLE", "instant", None, datetime.date.today() + datetime.timedelta(1), # today midnight None, {}, [], [], afterSibling=ModelXbrl.AUTO_LOCATE_ELEMENT) monetaryUnit = qname(XbrlConst.iso4217, "iso4217:USD") newUnit = instance.createUnit([monetaryUnit],[], afterSibling=ModelXbrl.AUTO_LOCATE_ELEMENT) nonNumAttr = [("contextRef", newCntx.id)] monetaryAttr = [("contextRef", newCntx.id), ("unitRef", newUnit.id), ("decimals", "2")] isoLanguage = qname("{http://www.xbrl.org/2005/iso639}iso639:en") # root of GL is accounting entries tuple xbrlElt = instance.modelDocument.xmlRootElement '''The container for XBRL GL, accountingEntries, is not the root of an XBRL GL file - the root, as with all XBRL files, is xbrl. This means that a single XBRL GL file can store one or more virtual XBRL GL files, through one or more accountingEntries structures with data inside. The primary key to understanding an XBRL GL file is the entriesType. A single physical XBRL GL file can have multiple accountingEntries structures to represent both transactions and master files; the differences are signified by the appropriate entriesType enumerated values.''' accountingEntries = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountingEntries")) # Because entriesType is strongly suggested, documentInfo will be required docInfo = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:documentInfo"), parent=accountingEntries) # This field, entriesType, provides the automated guidance on the purpose of the XBRL GL information. instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entriesType"), parent=docInfo, attributes=nonNumAttr, text=glEntriesType[qbReport]) '''Like a serial number, this field, uniqueID, provides a place to uniquely identify/track a series of entries. It is like less relevant for ad-hoc reports. XBRL GL provides for later correction through replacement or augmentation of transferred information.''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:uniqueID"), parent=docInfo, attributes=nonNumAttr, text="001") instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:language"), parent=docInfo, attributes=nonNumAttr, text=XmlUtil.addQnameValue(xbrlElt, isoLanguage)) '''The date associated with the creation of the data reflected within the associated accountingEntries section. Somewhat like a "printed date" on a paper report''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:creationDate"), parent=docInfo, attributes=nonNumAttr, text=str(datetime.date.today())) instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:periodCoveredStart"), parent=docInfo, attributes=nonNumAttr, text=fromDate) instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:periodCoveredEnd"), parent=docInfo, attributes=nonNumAttr, text=toDate) instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:sourceApplication"), parent=docInfo, attributes=nonNumAttr, text=docEltText(companyQbDoc, "ProductName","QuickBooks (version not known)")) instance.createFact(qname("{http://www.xbrl.org/int/gl/muc/2006-10-25}gl-muc:defaultCurrency"), parent=docInfo, attributes=nonNumAttr, text=XmlUtil.addQnameValue(xbrlElt, monetaryUnit)) '''Typically, an export from an accounting system does not carry with it information specifically about the company. However, the name of the company would be a very good thing to include with the file, making the entityInformation tuple necessary.''' entityInfo = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entityInformation"), parent=accountingEntries) '''The name of the company would be a very good thing to include with the file; this structure and its content are where that would be stored.''' orgIds = instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:organizationIdentifiers"), parent=entityInfo) instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:organizationIdentifier"), parent=orgIds, attributes=nonNumAttr, text=docEltText(companyQbDoc, "CompanyName")) instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:organizationDescription"), parent=orgIds, attributes=nonNumAttr, text=docEltText(companyQbDoc, "LegalCompanyName")) if qbReport == "trialBalance": qbTxnType = "trialbalance" else: qbTxnType = None qbTxnNumber = None qbDate = None qbRefNumber = None isFirst = True entryNumber = 1 lineNumber = 1 for dataRowElt in responseQbDoc.iter("DataRow"): cols = dict((colIdType[colElt.get("colID")], colElt.get("value")) for colElt in dataRowElt.iter("ColData")) if qbReport == "trialBalance" and "Label" in cols: cols["SplitAccount"] = cols["Label"] hasRowDataAccount = False for rowDataElt in dataRowElt.iter("RowData"): rowType = rowDataElt.get("rowType") if rowType == "account": hasRowDataAccount = True if "SplitAccount" not in cols: cols["SplitAccount"] = rowDataElt.get("value") if qbReport == "trialBalance" and not hasRowDataAccount: continue # skip total lines or others without account information elif qbReport in ("generalLedger", "journal"): if "TxnType" not in cols: continue # not a reportable entry # entry header fields only on new item that generates an entry header if "TxnType" in cols: qbTxnType = cols["TxnType"] if "TxnNumber" in cols: qbTxnNumber = cols["TxnNumber"] if "Date" in cols: qbDate = cols["Date"] if "RefNumber" in cols: qbRefNumber = cols["RefNumber"] # entry details provided on every entry qbName = cols.get("Name") qbMemo = cols.get("Memo") qbAccount = cols.get("SplitAccount") qbAmount = cols.get("Amount") qbDebitAmount = cols.get("Debit") qbCreditAmount = cols.get("Credit") runningBalance = cols.get("RunningBalance") if qbAmount is not None: drCrCode = None amt = qbAmount elif qbDebitAmount is not None: drCrCode = "D" amt = qbDebitAmount elif qbCreditAmount is not None: drCrCode = "C" amt = qbCreditAmount else: # no amount, skip this transaction continue if isFirst or qbTxnNumber: '''Journal entries require entry in entryHeader and entryDetail. Few files can be represented using only documentInfo and entityInformation sections, but it is certainly possible.''' entryHdr = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryHeader"), parent=accountingEntries) #instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:enteredBy"), parent=entryHdr, attributes=nonNumAttr, text="") instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:enteredDate"), parent=entryHdr, attributes=nonNumAttr, text=str(datetime.date.today())) '''This is an enumerated entry that ties the source journal from the reporting organization to a fixed list that helps in data interchange.''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:sourceJournalID"), parent=entryHdr, attributes=nonNumAttr, text="gj") '''Since sourceJournalID is enumerated (you must pick one of the entries already identified within XBRL GL), sourceJournalDescription lets you capture the actual code or term used to descibe the source journal by the organization.''' # instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:sourceJournalDescription"), parent=entryHdr, attributes=nonNumAttr, text="JE") '''An enumerated field to differentiate between details that represent actual accounting entries - as opposed to entries for budget purposes, planning purposes, or other entries that may not contribute to the financial statements.''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryType"), parent=entryHdr, attributes=nonNumAttr, text="standard") '''When capturing journal entries, you have a series of debits and credits that (normally) add up to zero. The hierarchical nature of XBRL GL keeps the entry detail lines associated with the entry header by a parent-child relationship. The unique identifier of each entry is entered here.''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryNumber"), parent=entryHdr, attributes=nonNumAttr, text=str(entryNumber)) entryNumber += 1 # The reason for making an entry goes here. if qbRefNumber: instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryComment"), parent=entryHdr, attributes=nonNumAttr, text=qbRefNumber) '''Individual lines of journal entries will normally require their own entryDetail section - one primary amount per entryDetail line. However, you can list different accounts within the same entryDetail line that are associated with that amount. For example, if you capitalize for US GAAP and expense for IFRS''' entryDetail = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryDetail"), parent=entryHdr) # A unique identifier for each entry detail line within an entry header, this should at the least be a counter. instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:lineNumber"), parent=entryDetail, attributes=nonNumAttr, text=str(lineNumber)) lineNumber += 1 '''If account information is represented elsewhere or as a master file, some of the fields below would not need to be here (signified by *)''' account = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:account"), parent=entryDetail) '''The account number is the basis for posting journal entries. In some cases, accounting systems used by small organizations do not use account numbers/codes, but only use a descriptive name for the account.''' # QB does not have account numbers # instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountMainID"), parent=account, attributes=nonNumAttr, text="10100") '''In most cases, the description is given to help a human reader; the accountMainID would be sufficient for data exchange purposes. As noted previously, some implementations use the description as the primary identifier of the account.''' if qbAccount: instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountMainDescription"), parent=account, attributes=nonNumAttr, text=qbAccount) '''Accounts serve many purposes, and in a large company using more sophisticated software, the company may wish to record the account used for the original entry and a separate consolidating account. The Japanese system may require a counterbalancing account for each line item. And an entry may be recorded differently for US GAAP, IFRS and other purposes. This code is an enumerated code to help identify accounts for those purposes.''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountPurposeCode"), parent=account, attributes=nonNumAttr, text="usgaap") '''In an international environment, the "chart of accounts" will include not only traditional accounts, like Cash, Accounts Payable/Due to Creditors or Retained Earnings, but also extensions to some of the accounts. Accounts Payable may be extended to include the creditors/vendors themselves. Therefore, in XBRL GL, accounts can be specifically identified as the "traditional" accountm or to identify a customer, vendor, employee, bank, job or fixed asset. While this may overlap with the customers, vendors and employees of the identifier structure, fixed-assets in the measurable structure, jobs in the jobInfo structure and other representations, they can also be represented here as appropriate to the jurisidiction.''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountType"), parent=account, attributes=nonNumAttr, text="account") '''What is a journal entry without a (monetary) amount? While XBRL GL may usher in journal entries that also incorporate quantities, to reflect the detail of business metrics, the (monetary) amount is another key and obvious fields. XBRL GL has been designed to reflect how popular accounting systems store amounts - some combination of a signed amount (e.g., 5, -10), a separate sign (entered into signOfAmount) and a separate place to indicate the number is associated with a debit or credit (debitCreditCode).''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:amount"), parent=entryDetail, attributes=monetaryAttr, text=amt) '''Depending on the originating system, this field may contain whether the amount is associated with a debit or credit. Interpreting the number correctly for import requires an understanding of the three related amount fields - amount, debitCreditCode and sign of amount.''' if drCrCode: instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:debitCreditCode"), parent=entryDetail, attributes=nonNumAttr, text=drCrCode) '''Depending on the originating system, this field may contain whether the amount is signed (+ or -) separately from the amount field itself. Interpreting the number correctly for import requires an understanding of the three related amount fields - amount, debitCreditCode and sign of amount.''' # instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:signOfAmount"), parent=entryDetail, attributes=nonNumAttr, text="+") # This date is the accounting significance date, not the date that entries were actually entered or posted to the system. if qbDate: instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:postingDate"), parent=entryDetail, attributes=nonNumAttr, text=qbDate) if qbName or qbMemo: identRef = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierReference"), parent=entryDetail) if qbMemo: instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierCode"), parent=identRef, attributes=nonNumAttr, text=qbMemo) if qbName: instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierDescription"), parent=identRef, attributes=nonNumAttr, text=qbName) #instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierType"), parent=identRef, attributes=nonNumAttr, # text="V") if qbReport != "trialBalance": if qbTxnType: # not exactly same enumerations as expected by QB cleanedQbTxnType = qbTxnType.replace(" ","").lower() glDocType = qbTxnTypeToGL.get(cleanedQbTxnType) # try table lookup if glDocType is None: # not in table if cleanedQbTxnType.endswith("check"): # didn't convert, probably should be a check glDocType = "check" # TBD add more QB transations here as they are discovered and not in table else: glDocType = qbTxnType # if all else fails pass through QB TxnType, it will fail GL validation and be noticed! instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:documentType"), parent=entryDetail, attributes=nonNumAttr, text=glDocType) '''This enumerated field is used to specifically state whether the entries have been posted to the originating system or not.''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:postingStatus"), parent=entryDetail, attributes=nonNumAttr, text="posted") # A comment at the individual entry detail level. # instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:detailComment"), parent=entryDetail, attributes=nonNumAttr, text="Comment...") isFirst = False if saveInstance: qbRequestStatus[ticket] = _("Saving XBRL-GL instance") instance.saveInstance() qbRequestStatus[ticket] = _("Done") # TBD resolve errors instance.errors = [] # TBD fix this xbrlInstances[ticket] = instance.uuid
def produceOutputFact(xpCtx, formula, result): priorErrorCount = len(xpCtx.modelXbrl.errors) # assemble context conceptQname = aspectValue(xpCtx, formula, Aspect.CONCEPT, "xbrlfe:missingConceptRule") if isinstance(conceptQname, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} concept: {1}").format(formula, conceptQname.msg), "err", conceptQname.err) modelConcept = None else: modelConcept = xpCtx.modelXbrl.qnameConcepts[conceptQname] if modelConcept is None or not modelConcept.isItem: xpCtx.modelXbrl.error( _("Formula {0} concept {1} is not an item").format( formula, conceptQname), "err", "xbrlfe:missingConceptRule") # entity entityIdentScheme = aspectValue(xpCtx, formula, Aspect.SCHEME, "xbrlfe:missingEntityIdentifierRule") if isinstance(entityIdentScheme, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} entity identifier scheme: {1}").format( formula, entityIdentScheme.msg), "err", str(entityIdentScheme)) entityIdentValue = None else: entityIdentValue = aspectValue(xpCtx, formula, Aspect.VALUE, "xbrlfe:missingEntityIdentifierRule") if isinstance(entityIdentValue, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} entity identifier value: {1}").format( formula, entityIdentValue.msg), "err", str(entityIdentScheme)) # period periodType = aspectValue(xpCtx, formula, Aspect.PERIOD_TYPE, "xbrlfe:missingPeriodRule") periodStart = None periodEndInstant = None if isinstance(periodType, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} period type: {1}").format(formula, periodType.msg), "err", str(periodType)) elif periodType == "instant": periodEndInstant = aspectValue(xpCtx, formula, Aspect.INSTANT, "xbrlfe:missingPeriodRule") if isinstance(periodEndInstant, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} period start: {1}").format( formula, periodEndInstant.msg), "err", str(periodEndInstant)) elif periodType == "duration": periodStart = aspectValue(xpCtx, formula, Aspect.START, "xbrlfe:missingPeriodRule") if isinstance(periodStart, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} period start: {1}").format( formula, periodStart.msg), "err", str(periodStart)) periodEndInstant = aspectValue(xpCtx, formula, Aspect.END, "xbrlfe:missingPeriodRule") if isinstance(periodEndInstant, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} period end: {1}").format( formula, periodEndInstant.msg), "err", str(periodEndInstant)) # unit if modelConcept and modelConcept.isNumeric: unitSource = aspectValue(xpCtx, formula, Aspect.UNIT_MEASURES, None) multDivBy = aspectValue(xpCtx, formula, Aspect.MULTIPLY_BY, "xbrlfe:missingUnitRule") if isinstance(multDivBy, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} unit: {1}").format(formula, multDivBy.msg), "err", str(multDivBy) if isinstance(multDivBy, VariableBindingError) else "xbrlfe:missingUnitRule") multiplyBy = () divideBy = () # prevent errors later if bad else: divMultBy = aspectValue(xpCtx, formula, Aspect.DIVIDE_BY, "xbrlfe:missingUnitRule") if isinstance(divMultBy, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} unit: {1}").format(formula, divMultBy.msg), "err", str(multDivBy) if isinstance(divMultBy, VariableBindingError) else "xbrlfe:missingUnitRule") multiplyBy = () divideBy = () # prevent errors later if bad else: multiplyBy = unitSource[0] + multDivBy[0] + divMultBy[1] divideBy = unitSource[1] + multDivBy[1] + divMultBy[0] # remove cancelling mult/div units lookForCommonUnits = True while lookForCommonUnits: lookForCommonUnits = False for commonUnit in multiplyBy: if commonUnit in divideBy: multiplyBy.remove(commonUnit) divideBy.remove(commonUnit) lookForCommonUnits = True break if len(multiplyBy) == 0: # if no units add pure multiplyBy.append(XbrlConst.qnXbrliPure) # dimensions segOCCs = [] scenOCCs = [] if formula.aspectModel == "dimensional": dimAspects = {} dimQnames = aspectValue(xpCtx, formula, Aspect.DIMENSIONS, None) if dimQnames: for dimQname in dimQnames: dimConcept = xpCtx.modelXbrl.qnameConcepts[dimQname] dimErr = "xbrlfe:missing{0}DimensionRule".format( "typed" if dimConcept and dimConcept.isTypedDimension else "explicit") dimValue = aspectValue(xpCtx, formula, dimQname, dimErr) if isinstance(dimValue, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} dimension {1}: {2}").format( formula, dimQname, dimValue.msg), "err", dimErr) elif dimValue and xpCtx.modelXbrl.qnameDimensionDefaults.get( dimQname) != dimValue: dimAspects[dimQname] = dimValue segOCCs = aspectValue(xpCtx, formula, Aspect.NON_XDT_SEGMENT, None) scenOCCs = aspectValue(xpCtx, formula, Aspect.NON_XDT_SCENARIO, None) else: dimAspects = None # non-dimensional segOCCs = aspectValue(xpCtx, formula, Aspect.COMPLETE_SEGMENT, None) scenOCCs = aspectValue(xpCtx, formula, Aspect.COMPLETE_SCENARIO, None) if priorErrorCount < len(xpCtx.modelXbrl.errors): return None # had errors, don't produce output fact # does context exist in out instance document outputInstanceQname = formula.outputInstanceQname outputXbrlInstance = xpCtx.inScopeVars[outputInstanceQname] xbrlElt = outputXbrlInstance.modelDocument.xmlRootElement # in source instance document # add context prevCntx = outputXbrlInstance.matchContext(entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, dimAspects, segOCCs, scenOCCs) if prevCntx: cntxId = prevCntx.id newCntxElt = prevCntx.element else: cntxId = 'c-{0:02n}'.format(len(outputXbrlInstance.contexts) + 1) newCntxElt = XmlUtil.addChild( xbrlElt, XbrlConst.xbrli, "context", attributes=("id", cntxId), afterSibling=xpCtx.outputLastContext.get(outputInstanceQname)) xpCtx.outputLastContext[outputInstanceQname] = newCntxElt entityElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "entity") XmlUtil.addChild(entityElt, XbrlConst.xbrli, "identifier", attributes=("scheme", entityIdentScheme), text=entityIdentValue) periodElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "period") if periodType == "forever": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "forever") elif periodType == "instant": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "instant", text=XmlUtil.dateunionValue(periodEndInstant, subtractOneDay=True)) elif periodType == "duration": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "startDate", text=XmlUtil.dateunionValue(periodStart)) XmlUtil.addChild(periodElt, XbrlConst.xbrli, "endDate", text=XmlUtil.dateunionValue(periodEndInstant, subtractOneDay=True)) segmentElt = None scenarioElt = None from arelle.ModelObject import ModelDimensionValue if dimAspects: for dimQname in sorted(dimAspects.keys()): dimValue = dimAspects[dimQname] if isinstance(dimValue, ModelDimensionValue): if dimValue.isExplicit: dimMemberQname = dimValue.memberQname contextEltName = dimValue.contextElement else: # qname for explicit or node for typed dimMemberQname = dimValue contextEltName = xpCtx.modelXbrl.qnameDimensionContextElement.get( dimQname) if contextEltName == "segment": if not segmentElt: segmentElt = XmlUtil.addChild(entityElt, XbrlConst.xbrli, "segment") contextElt = segmentElt elif contextEltName == "scenario": if not scenarioElt: scenarioElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "scenario") contextElt = scenarioElt else: continue dimConcept = xpCtx.modelXbrl.qnameConcepts[dimQname] dimAttr = ("dimension", XmlUtil.addQnameValue(xbrlElt, dimConcept.qname)) if dimConcept.isTypedDimension: dimElt = XmlUtil.addChild(contextElt, XbrlConst.xbrldi, "typedMember", attributes=dimAttr) if isinstance(dimValue, ModelDimensionValue) and dimValue.isTyped: XmlUtil.copyChildren(dimElt, dimValue.typedMember) elif dimMemberQname: dimElt = XmlUtil.addChild(contextElt, XbrlConst.xbrldi, "explicitMember", attributes=dimAttr, text=XmlUtil.addQnameValue( xbrlElt, dimMemberQname)) if segOCCs: if not segmentElt: segmentElt = XmlUtil.addChild(entityElt, XbrlConst.xbrli, "segment") XmlUtil.copyNodes(segmentElt, segOCCs) if scenOCCs: if not scenarioElt: scenarioElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "scenario") XmlUtil.copyNodes(scenarioElt, scenOCCs) outputXbrlInstance.modelDocument.contextDiscover(newCntxElt) # does unit exist # add unit if modelConcept.isNumeric: prevUnit = outputXbrlInstance.matchUnit(multiplyBy, divideBy) if prevUnit: unitId = prevUnit.id newUnitElt = prevUnit.element else: unitId = 'u-{0:02n}'.format(len(outputXbrlInstance.units) + 1) newUnitElt = XmlUtil.addChild( xbrlElt, XbrlConst.xbrli, "unit", attributes=("id", unitId), afterSibling=xpCtx.outputLastUnit.get(outputInstanceQname)) xpCtx.outputLastUnit[outputInstanceQname] = newUnitElt if len(divideBy) == 0: for multiply in multiplyBy: XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue( xbrlElt, multiply)) else: divElt = XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "divide") numElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitNumerator") denElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitDenominator") for multiply in multiplyBy: XmlUtil.addChild(numElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue( xbrlElt, multiply)) for divide in divideBy: XmlUtil.addChild(denElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue( xbrlElt, divide)) outputXbrlInstance.modelDocument.unitDiscover(newUnitElt) # add fact attrs = [("contextRef", cntxId)] precision = None decimals = None if modelConcept.isNumeric: attrs.append(("unitRef", unitId)) value = formula.evaluate(xpCtx) valueSeqLen = len(value) if valueSeqLen > 1: xpCtx.modelXbrl.error( _("Formula {0} value is a sequence of length {1}").format( formula, valueSeqLen), "err", "xbrlfe:nonSingletonOutputValue") else: if valueSeqLen == 0: #xsi:nil if no value attrs.append((XbrlConst.qnXsiNil, "true")) v = None else: # add precision/decimals for non-fraction numerics if modelConcept.isNumeric and not modelConcept.isFraction: if formula.hasDecimals: decimals = formula.evaluateRule(xpCtx, Aspect.DECIMALS) attrs.append(("decimals", decimals)) else: if formula.hasPrecision: precision = formula.evaluateRule( xpCtx, Aspect.PRECISION) else: precision = 0 attrs.append(("precision", precision)) x = value[0] if isinstance(x, float): from math import (log10, isnan, isinf, fabs) if (isnan(x) or (precision and (isinf(precision) or precision == 0)) or (decimals and isinf(decimals))): v = string(xpCtx, x) elif decimals is not None: v = "%.*f" % (int(decimals), x) elif precision is not None: a = fabs(x) log = log10(a) if a != 0 else 0 v = "%.*f" % (int(precision) - int(log) - (1 if a >= 1 else 0), x) else: # no implicit precision yet v = string(xpCtx, x) elif isinstance(x, QName): v = XmlUtil.addQnameValue(xbrlElt, x) elif isinstance(x, datetime.datetime): v = XmlUtil.dateunionValue(x) else: v = string(xpCtx, x) itemElt = XmlUtil.addChild( xbrlElt, conceptQname, attributes=attrs, text=v, afterSibling=xpCtx.outputLastFact.get(outputInstanceQname)) xpCtx.outputLastFact[outputInstanceQname] = itemElt newFact = outputXbrlInstance.modelDocument.factDiscover( itemElt, outputXbrlInstance.facts) return newFact
def produceOutputFact(xpCtx, formula, result): priorErrorCount = len(xpCtx.modelXbrl.errors) # assemble context conceptQname = aspectValue(xpCtx, formula, Aspect.CONCEPT, "xbrlfe:missingConceptRule") if isinstance(conceptQname, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} concept: {1}").format( formula, conceptQname.msg), "err", conceptQname.err) modelConcept = None else: modelConcept = xpCtx.modelXbrl.qnameConcepts[conceptQname] if modelConcept is None or not modelConcept.isItem: xpCtx.modelXbrl.error( _("Formula {0} concept {1} is not an item").format( formula, conceptQname), "err", "xbrlfe:missingConceptRule") # entity entityIdentScheme = aspectValue(xpCtx, formula, Aspect.SCHEME, "xbrlfe:missingEntityIdentifierRule") if isinstance(entityIdentScheme, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} entity identifier scheme: {1}").format( formula, entityIdentScheme.msg ), "err", str(entityIdentScheme)) entityIdentValue = None else: entityIdentValue = aspectValue(xpCtx, formula, Aspect.VALUE, "xbrlfe:missingEntityIdentifierRule") if isinstance(entityIdentValue, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} entity identifier value: {1}").format( formula, entityIdentValue.msg ), "err", str(entityIdentScheme)) # period periodType = aspectValue(xpCtx, formula, Aspect.PERIOD_TYPE, "xbrlfe:missingPeriodRule") periodStart = None periodEndInstant = None if isinstance(periodType, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} period type: {1}").format( formula, periodType.msg ), "err", str(periodType)) elif periodType == "instant": periodEndInstant = aspectValue(xpCtx, formula, Aspect.INSTANT, "xbrlfe:missingPeriodRule") if isinstance(periodEndInstant, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} period start: {1}").format( formula, periodEndInstant.msg ), "err", str(periodEndInstant)) elif periodType == "duration": periodStart = aspectValue(xpCtx, formula, Aspect.START, "xbrlfe:missingPeriodRule") if isinstance(periodStart, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} period start: {1}").format( formula, periodStart.msg ), "err", str(periodStart)) periodEndInstant = aspectValue(xpCtx, formula, Aspect.END, "xbrlfe:missingPeriodRule") if isinstance(periodEndInstant, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} period end: {1}").format( formula, periodEndInstant.msg ), "err", str(periodEndInstant)) # unit if modelConcept and modelConcept.isNumeric: unitSource = aspectValue(xpCtx, formula, Aspect.UNIT_MEASURES, None) multDivBy = aspectValue(xpCtx, formula, Aspect.MULTIPLY_BY, "xbrlfe:missingUnitRule") if isinstance(multDivBy, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} unit: {1}").format( formula, multDivBy.msg ), "err", str(multDivBy) if isinstance(multDivBy, VariableBindingError) else "xbrlfe:missingUnitRule") multiplyBy = (); divideBy = () # prevent errors later if bad else: divMultBy = aspectValue(xpCtx, formula, Aspect.DIVIDE_BY, "xbrlfe:missingUnitRule") if isinstance(divMultBy, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} unit: {1}").format( formula, divMultBy.msg ), "err", str(multDivBy) if isinstance(divMultBy, VariableBindingError) else "xbrlfe:missingUnitRule") multiplyBy = (); divideBy = () # prevent errors later if bad else: multiplyBy = unitSource[0] + multDivBy[0] + divMultBy[1] divideBy = unitSource[1] + multDivBy[1] + divMultBy[0] # remove cancelling mult/div units lookForCommonUnits = True while lookForCommonUnits: lookForCommonUnits = False for commonUnit in multiplyBy: if commonUnit in divideBy: multiplyBy.remove(commonUnit) divideBy.remove(commonUnit) lookForCommonUnits = True break if len(multiplyBy) == 0: # if no units add pure multiplyBy.append(XbrlConst.qnXbrliPure) # dimensions segOCCs = [] scenOCCs = [] if formula.aspectModel == "dimensional": dimAspects = {} dimQnames = aspectValue(xpCtx, formula, Aspect.DIMENSIONS, None) if dimQnames: for dimQname in dimQnames: dimConcept = xpCtx.modelXbrl.qnameConcepts[dimQname] dimErr = "xbrlfe:missing{0}DimensionRule".format("typed" if dimConcept and dimConcept.isTypedDimension else "explicit") dimValue = aspectValue(xpCtx, formula, dimQname, dimErr) if isinstance(dimValue, VariableBindingError): xpCtx.modelXbrl.error( _("Formula {0} dimension {1}: {2}").format( formula, dimQname, dimValue.msg ), "err", dimErr) elif dimValue and xpCtx.modelXbrl.qnameDimensionDefaults.get(dimQname) != dimValue: dimAspects[dimQname] = dimValue segOCCs = aspectValue(xpCtx, formula, Aspect.NON_XDT_SEGMENT, None) scenOCCs = aspectValue(xpCtx, formula, Aspect.NON_XDT_SCENARIO, None) else: dimAspects = None # non-dimensional segOCCs = aspectValue(xpCtx, formula, Aspect.COMPLETE_SEGMENT, None) scenOCCs = aspectValue(xpCtx, formula, Aspect.COMPLETE_SCENARIO, None) if priorErrorCount < len(xpCtx.modelXbrl.errors): return None # had errors, don't produce output fact # does context exist in out instance document outputInstanceQname = formula.outputInstanceQname outputXbrlInstance = xpCtx.inScopeVars[outputInstanceQname] xbrlElt = outputXbrlInstance.modelDocument.xmlRootElement # in source instance document # add context prevCntx = outputXbrlInstance.matchContext( entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, dimAspects, segOCCs, scenOCCs) if prevCntx: cntxId = prevCntx.id newCntxElt = prevCntx.element else: cntxId = 'c-{0:02n}'.format( len(outputXbrlInstance.contexts) + 1) newCntxElt = XmlUtil.addChild(xbrlElt, XbrlConst.xbrli, "context", attributes=("id", cntxId), afterSibling=xpCtx.outputLastContext.get(outputInstanceQname)) xpCtx.outputLastContext[outputInstanceQname] = newCntxElt entityElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "entity") XmlUtil.addChild(entityElt, XbrlConst.xbrli, "identifier", attributes=("scheme", entityIdentScheme), text=entityIdentValue) periodElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "period") if periodType == "forever": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "forever") elif periodType == "instant": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "instant", text=XmlUtil.dateunionValue(periodEndInstant, subtractOneDay=True)) elif periodType == "duration": XmlUtil.addChild(periodElt, XbrlConst.xbrli, "startDate", text=XmlUtil.dateunionValue(periodStart)) XmlUtil.addChild(periodElt, XbrlConst.xbrli, "endDate", text=XmlUtil.dateunionValue(periodEndInstant, subtractOneDay=True)) segmentElt = None scenarioElt = None from arelle.ModelObject import ModelDimensionValue if dimAspects: for dimQname in sorted(dimAspects.keys()): dimValue = dimAspects[dimQname] if isinstance(dimValue, ModelDimensionValue): if dimValue.isExplicit: dimMemberQname = dimValue.memberQname contextEltName = dimValue.contextElement else: # qname for explicit or node for typed dimMemberQname = dimValue contextEltName = xpCtx.modelXbrl.qnameDimensionContextElement.get(dimQname) if contextEltName == "segment": if not segmentElt: segmentElt = XmlUtil.addChild(entityElt, XbrlConst.xbrli, "segment") contextElt = segmentElt elif contextEltName == "scenario": if not scenarioElt: scenarioElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "scenario") contextElt = scenarioElt else: continue dimConcept = xpCtx.modelXbrl.qnameConcepts[dimQname] dimAttr = ("dimension", XmlUtil.addQnameValue(xbrlElt, dimConcept.qname)) if dimConcept.isTypedDimension: dimElt = XmlUtil.addChild(contextElt, XbrlConst.xbrldi, "typedMember", attributes=dimAttr) if isinstance(dimValue, ModelDimensionValue) and dimValue.isTyped: XmlUtil.copyChildren(dimElt, dimValue.typedMember) elif dimMemberQname: dimElt = XmlUtil.addChild(contextElt, XbrlConst.xbrldi, "explicitMember", attributes=dimAttr, text=XmlUtil.addQnameValue(xbrlElt, dimMemberQname)) if segOCCs: if not segmentElt: segmentElt = XmlUtil.addChild(entityElt, XbrlConst.xbrli, "segment") XmlUtil.copyNodes(segmentElt, segOCCs) if scenOCCs: if not scenarioElt: scenarioElt = XmlUtil.addChild(newCntxElt, XbrlConst.xbrli, "scenario") XmlUtil.copyNodes(scenarioElt, scenOCCs) outputXbrlInstance.modelDocument.contextDiscover(newCntxElt) # does unit exist # add unit if modelConcept.isNumeric: prevUnit = outputXbrlInstance.matchUnit(multiplyBy, divideBy) if prevUnit: unitId = prevUnit.id newUnitElt = prevUnit.element else: unitId = 'u-{0:02n}'.format( len(outputXbrlInstance.units) + 1) newUnitElt = XmlUtil.addChild(xbrlElt, XbrlConst.xbrli, "unit", attributes=("id", unitId), afterSibling=xpCtx.outputLastUnit.get(outputInstanceQname)) xpCtx.outputLastUnit[outputInstanceQname] = newUnitElt if len(divideBy) == 0: for multiply in multiplyBy: XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, multiply)) else: divElt = XmlUtil.addChild(newUnitElt, XbrlConst.xbrli, "divide") numElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitNumerator") denElt = XmlUtil.addChild(divElt, XbrlConst.xbrli, "unitDenominator") for multiply in multiplyBy: XmlUtil.addChild(numElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, multiply)) for divide in divideBy: XmlUtil.addChild(denElt, XbrlConst.xbrli, "measure", text=XmlUtil.addQnameValue(xbrlElt, divide)) outputXbrlInstance.modelDocument.unitDiscover(newUnitElt) # add fact attrs = [("contextRef", cntxId)] precision = None decimals = None if modelConcept.isNumeric: attrs.append(("unitRef", unitId)) value = formula.evaluate(xpCtx) valueSeqLen = len(value) if valueSeqLen > 1: xpCtx.modelXbrl.error( _("Formula {0} value is a sequence of length {1}").format( formula, valueSeqLen ), "err", "xbrlfe:nonSingletonOutputValue") else: if valueSeqLen == 0: #xsi:nil if no value attrs.append((XbrlConst.qnXsiNil, "true")) v = None else: # add precision/decimals for non-fraction numerics if modelConcept.isNumeric and not modelConcept.isFraction: if formula.hasDecimals: decimals = formula.evaluateRule(xpCtx, Aspect.DECIMALS) attrs.append(("decimals", decimals)) else: if formula.hasPrecision: precision = formula.evaluateRule(xpCtx, Aspect.PRECISION) else: precision = 0 attrs.append(("precision", precision)) x = value[0] if isinstance(x,float): from math import (log10, isnan, isinf, fabs) if (isnan(x) or (precision and (isinf(precision) or precision == 0)) or (decimals and isinf(decimals))): v = string(xpCtx, x) elif decimals is not None: v = "%.*f" % ( int(decimals), x) elif precision is not None: a = fabs(x) log = log10(a) if a != 0 else 0 v = "%.*f" % ( int(precision) - int(log) - (1 if a >= 1 else 0), x) else: # no implicit precision yet v = string(xpCtx, x) elif isinstance(x,QName): v = XmlUtil.addQnameValue(xbrlElt, x) elif isinstance(x,datetime.datetime): v = XmlUtil.dateunionValue(x) else: v = string(xpCtx, x) itemElt = XmlUtil.addChild(xbrlElt, conceptQname, attributes=attrs, text=v, afterSibling=xpCtx.outputLastFact.get(outputInstanceQname)) xpCtx.outputLastFact[outputInstanceQname] = itemElt newFact = outputXbrlInstance.modelDocument.factDiscover(itemElt, outputXbrlInstance.facts) return newFact
def produceOutputFact(xpCtx, formula, result): priorErrorCount = len(xpCtx.modelXbrl.errors) isTuple = isinstance(formula,ModelTuple) # assemble context conceptQname = aspectValue(xpCtx, formula, Aspect.CONCEPT, "xbrlfe:missingConceptRule") if isinstance(conceptQname, VariableBindingError): xpCtx.modelXbrl.error(conceptQname.err, _("Formula %(xlinkLabel)s concept: %(concept)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, concept=conceptQname.msg) modelConcept = None else: modelConcept = xpCtx.modelXbrl.qnameConcepts[conceptQname] if modelConcept is None or (not modelConcept.isTuple if isTuple else not modelConcept.isItem): xpCtx.modelXbrl.error("xbrlfe:missingConceptRule", _("Formula %(xlinkLabel)s concept %(concept)s is not a %(element)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, concept=conceptQname, element=formula.localName) outputLocation = aspectValue(xpCtx, formula, Aspect.LOCATION_RULE, None) if not isTuple: # entity entityIdentScheme = aspectValue(xpCtx, formula, Aspect.SCHEME, "xbrlfe:missingEntityIdentifierRule") if isinstance(entityIdentScheme, VariableBindingError): xpCtx.modelXbrl.error(str(entityIdentScheme), _("Formula %(xlinkLabel)s entity identifier scheme: %(scheme)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, scheme=entityIdentScheme.msg) entityIdentValue = None else: entityIdentValue = aspectValue(xpCtx, formula, Aspect.VALUE, "xbrlfe:missingEntityIdentifierRule") if isinstance(entityIdentValue, VariableBindingError): xpCtx.modelXbrl.error(str(entityIdentScheme), _("Formula %(xlinkLabel)s entity identifier value: %(entityIdentifier)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, entityIdentifier=entityIdentValue.msg) # period periodType = aspectValue(xpCtx, formula, Aspect.PERIOD_TYPE, "xbrlfe:missingPeriodRule") periodStart = None periodEndInstant = None if isinstance(periodType, VariableBindingError): xpCtx.modelXbrl.error(str(periodType), _("Formula %(xlinkLabel)s period type: %(periodType)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, periodType=periodType.msg) elif periodType == "instant": periodEndInstant = aspectValue(xpCtx, formula, Aspect.INSTANT, "xbrlfe:missingPeriodRule") if isinstance(periodEndInstant, VariableBindingError): xpCtx.modelXbrl.error(str(periodEndInstant), _("Formula %(xlinkLabel)s period end: %(period)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, period=periodEndInstant.msg) elif periodType == "duration": periodStart = aspectValue(xpCtx, formula, Aspect.START, "xbrlfe:missingPeriodRule") if isinstance(periodStart, VariableBindingError): xpCtx.modelXbrl.error(str(periodStart), _("Formula %(xlinkLabel)s period start: %(period)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, period=periodStart.msg) periodEndInstant = aspectValue(xpCtx, formula, Aspect.END, "xbrlfe:missingPeriodRule") if isinstance(periodEndInstant, VariableBindingError): xpCtx.modelXbrl.error(str(periodEndInstant), _("Formula %(xlinkLabel)s period end: %(period)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, period=periodEndInstant.msg) # unit if modelConcept is not None and modelConcept.isNumeric: unitSource = aspectValue(xpCtx, formula, Aspect.UNIT_MEASURES, None) multDivBy = aspectValue(xpCtx, formula, Aspect.MULTIPLY_BY, "xbrlfe:missingUnitRule") if isinstance(multDivBy, VariableBindingError): xpCtx.modelXbrl.error(str(multDivBy) if isinstance(multDivBy, VariableBindingError) else "xbrlfe:missingUnitRule", _("Formula %(xlinkLabel)s unit: %(unit)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, unit=multDivBy.msg) multiplyBy = (); divideBy = () # prevent errors later if bad else: divMultBy = aspectValue(xpCtx, formula, Aspect.DIVIDE_BY, "xbrlfe:missingUnitRule") if isinstance(divMultBy, VariableBindingError): xpCtx.modelXbrl.error(str(multDivBy) if isinstance(divMultBy, VariableBindingError) else "xbrlfe:missingUnitRule", _("Formula %(xlinkLabel)s unit: %(unit)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, unit=divMultBy.msg) multiplyBy = (); divideBy = () # prevent errors later if bad else: multiplyBy = unitSource[0] + multDivBy[0] + divMultBy[1] divideBy = unitSource[1] + multDivBy[1] + divMultBy[0] # remove cancelling mult/div units lookForCommonUnits = True while lookForCommonUnits: lookForCommonUnits = False for commonUnit in multiplyBy: if commonUnit in divideBy: multiplyBy.remove(commonUnit) divideBy.remove(commonUnit) lookForCommonUnits = True break if len(multiplyBy) == 0: # if no units add pure multiplyBy.append(XbrlConst.qnXbrliPure) # dimensions segOCCs = [] scenOCCs = [] if formula.aspectModel == "dimensional": dimAspects = {} dimQnames = aspectValue(xpCtx, formula, Aspect.DIMENSIONS, None) if dimQnames: for dimQname in dimQnames: dimConcept = xpCtx.modelXbrl.qnameConcepts[dimQname] dimErr = "xbrlfe:missing{0}DimensionRule".format("typed" if dimConcept is not None and dimConcept.isTypedDimension else "explicit") dimValue = aspectValue(xpCtx, formula, dimQname, dimErr) if isinstance(dimValue, VariableBindingError): xpCtx.modelXbrl.error(dimErr, _("Formula %(xlinkLabel)s dimension %(dimension)s: %(value)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, dimension=dimQname, value=dimValue.msg) elif dimConcept.isTypedDimension: if isinstance(dimValue, list): # result of flatten, always a list if len(dimValue) != 1 or not isinstance(dimValue[0], ModelObject): xpCtx.modelXbrl.error("xbrlfe:wrongXpathResultForTypedDimensionRule", _("Formula %(xlinkLabel)s dimension %(dimension)s value is not a node: %(value)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, dimension=dimQname, value=dimValue) continue dimValue = dimValue[0] dimAspects[dimQname] = dimValue elif dimValue is not None and xpCtx.modelXbrl.qnameDimensionDefaults.get(dimQname) != dimValue: dimAspects[dimQname] = dimValue segOCCs = aspectValue(xpCtx, formula, Aspect.NON_XDT_SEGMENT, None) scenOCCs = aspectValue(xpCtx, formula, Aspect.NON_XDT_SCENARIO, None) for occElt in xpCtx.flattenSequence((segOCCs, scenOCCs)): if isinstance(occElt, ModelObject) and occElt.namespaceURI == XbrlConst.xbrldi: xpCtx.modelXbrl.error("xbrlfe:badSubsequentOCCValue", _("Formula %(xlinkLabel)s OCC element %(occ)s covers a dimensional aspect"), modelObject=(formula,occElt), xlinkLabel=formula.xlinkLabel, occ=occElt.elementQname) else: dimAspects = None # non-dimensional segOCCs = aspectValue(xpCtx, formula, Aspect.COMPLETE_SEGMENT, None) scenOCCs = aspectValue(xpCtx, formula, Aspect.COMPLETE_SCENARIO, None) if priorErrorCount < len(xpCtx.modelXbrl.errors): return None # had errors, don't produce output fact # does context exist in out instance document outputInstanceQname = formula.outputInstanceQname outputXbrlInstance = xpCtx.inScopeVars[outputInstanceQname] xbrlElt = outputXbrlInstance.modelDocument.xmlRootElement # in source instance document newFact = None if isTuple: newFact = outputXbrlInstance.createFact(conceptQname, parent=outputLocation, afterSibling=xpCtx.outputLastFact.get(outputInstanceQname)) else: # add context prevCntx = outputXbrlInstance.matchContext( entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, dimAspects, segOCCs, scenOCCs) if prevCntx is not None: cntxId = prevCntx.id newCntxElt = prevCntx else: newCntxElt = outputXbrlInstance.createContext(entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, conceptQname, dimAspects, segOCCs, scenOCCs, afterSibling=xpCtx.outputLastContext.get(outputInstanceQname), beforeSibling=xpCtx.outputFirstFact.get(outputInstanceQname)) cntxId = newCntxElt.id xpCtx.outputLastContext[outputInstanceQname] = newCntxElt # does unit exist # add unit if modelConcept.isNumeric: prevUnit = outputXbrlInstance.matchUnit(multiplyBy, divideBy) if prevUnit is not None: unitId = prevUnit.id newUnitElt = prevUnit else: newUnitElt = outputXbrlInstance.createUnit(multiplyBy, divideBy, afterSibling=xpCtx.outputLastUnit.get(outputInstanceQname), beforeSibling=xpCtx.outputFirstFact.get(outputInstanceQname)) unitId = newUnitElt.id xpCtx.outputLastUnit[outputInstanceQname] = newUnitElt # add fact attrs = [("contextRef", cntxId)] precision = None decimals = None if modelConcept.isNumeric: attrs.append(("unitRef", unitId)) value = formula.evaluate(xpCtx) valueSeqLen = len(value) if valueSeqLen > 1: xpCtx.modelXbrl.error("xbrlfe:nonSingletonOutputValue", _("Formula %(xlinkLabel)s value is a sequence of length %(valueSequenceLength)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, valueSequenceLength=valueSeqLen) else: if valueSeqLen == 0: #xsi:nil if no value attrs.append((XbrlConst.qnXsiNil, "true")) v = None else: # add precision/decimals for non-fraction numerics if modelConcept.isNumeric and not modelConcept.isFraction: if formula.hasDecimals: decimals = formula.evaluateRule(xpCtx, Aspect.DECIMALS) attrs.append(("decimals", decimals)) else: if formula.hasPrecision: precision = formula.evaluateRule(xpCtx, Aspect.PRECISION) else: precision = 0 attrs.append(("precision", precision)) x = value[0] if isinstance(x,float): from math import (log10, isnan, isinf, fabs) if (isnan(x) or (precision and (isinf(precision) or precision == 0)) or (decimals and isinf(decimals))): v = xsString(xpCtx, None, x) elif decimals is not None: v = "%.*f" % ( int(decimals), x) elif precision is not None and precision != 0: a = fabs(x) log = log10(a) if a != 0 else 0 v = "%.*f" % ( int(precision) - int(log) - (1 if a >= 1 else 0), x) else: # no implicit precision yet v = xsString(xpCtx, None, x) elif isinstance(x,QName): v = XmlUtil.addQnameValue(xbrlElt, x) elif isinstance(x,datetime.datetime): v = XmlUtil.dateunionValue(x) else: v = xsString(xpCtx, None, x) newFact = outputXbrlInstance.createFact(conceptQname, attributes=attrs, text=v, parent=outputLocation, afterSibling=xpCtx.outputLastFact.get(outputInstanceQname)) if newFact is not None: xpCtx.outputLastFact[outputInstanceQname] = newFact if outputInstanceQname not in xpCtx.outputFirstFact: xpCtx.outputFirstFact[outputInstanceQname] = newFact return newFact