예제 #1
0
 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
예제 #2
0
 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)
예제 #3
0
 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)
예제 #4
0
 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
예제 #5
0
 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
예제 #6
0
 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
예제 #7
0
    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
예제 #8
0
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
예제 #9
0
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
예제 #10
0
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
예제 #11
0
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