| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.fineract.mix.service; |
| |
| import java.math.BigDecimal; |
| import java.sql.Date; |
| import java.text.SimpleDateFormat; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import org.apache.fineract.mix.data.ContextData; |
| import org.apache.fineract.mix.data.MixTaxonomyData; |
| import org.apache.fineract.mix.data.NamespaceData; |
| import org.apache.fineract.mix.data.XBRLData; |
| import org.apache.fineract.mix.exception.XBRLMappingInvalidException; |
| import org.dom4j.Document; |
| import org.dom4j.DocumentHelper; |
| import org.dom4j.Element; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.stereotype.Component; |
| |
| @Component |
| public class XBRLBuilder { |
| |
| private static final String SCHEME_URL = "http://www.themix.org"; |
| private static final String IDENTIFIER = "000000"; |
| private static final String UNITID_PURE = "Unit1"; |
| private static final String UNITID_CUR = "Unit2"; |
| |
| private Element root; |
| private HashMap<ContextData, String> contextMap; |
| Date startDate; |
| Date endDate; |
| private Integer instantScenarioCounter = 1; |
| private Integer durationScenarioCounter = 1; |
| |
| @Autowired |
| private NamespaceReadPlatformService readNamespaceService; |
| |
| public String build(final XBRLData xbrlData) { |
| return this.build(xbrlData.getResultMap(), xbrlData.getStartDate(), xbrlData.getEndDate(), xbrlData.getCurrency()); |
| } |
| |
| public String build(final Map<MixTaxonomyData, BigDecimal> map, final Date startDate, final Date endDate, final String currency) { |
| this.instantScenarioCounter = 1; |
| this.durationScenarioCounter = 1; |
| this.contextMap = new HashMap<>(); |
| final Document doc = DocumentHelper.createDocument(); |
| this.root = doc.addElement("xbrl"); |
| |
| this.root.addElement("schemaRef").addNamespace("link", |
| "http://www.themix.org/sites/default/files/Taxonomy2010/dct/dc-all_2010-08-31.xsd"); |
| |
| this.startDate = startDate; |
| this.endDate = endDate; |
| |
| for (final Entry<MixTaxonomyData, BigDecimal> entry : map.entrySet()) { |
| final MixTaxonomyData taxonomy = entry.getKey(); |
| final BigDecimal value = entry.getValue(); |
| addTaxonomy(this.root, taxonomy, value); |
| |
| } |
| |
| addContexts(); |
| addCurrencyUnit(currency); |
| addNumberUnit(); |
| |
| doc.setXMLEncoding("UTF-8"); |
| |
| return doc.asXML(); |
| } |
| |
| Element addTaxonomy(final Element rootElement, final MixTaxonomyData taxonomy, final BigDecimal value) { |
| |
| // throw an error is start / endate is null |
| if (this.startDate == null || this.endDate == null) { throw new XBRLMappingInvalidException( |
| "start date and end date should not be null"); } |
| |
| final String prefix = taxonomy.getNamespace(); |
| String qname = taxonomy.getName(); |
| if (prefix != null && (!prefix.isEmpty())) { |
| final NamespaceData ns = this.readNamespaceService.retrieveNamespaceByPrefix(prefix); |
| if (ns != null) { |
| |
| this.root.addNamespace(prefix, ns.url()); |
| } |
| qname = prefix + ":" + taxonomy.getName(); |
| |
| } |
| final Element xmlElement = rootElement.addElement(qname); |
| |
| final String dimension = taxonomy.getDimension(); |
| final SimpleDateFormat timeFormat = new SimpleDateFormat("MM_dd_yyyy"); |
| |
| ContextData context = null; |
| if (dimension != null) { |
| final String[] dims = dimension.split(":"); |
| |
| if (dims.length == 2) { |
| context = new ContextData(dims[0], dims[1], taxonomy.getType()); |
| if (this.contextMap.containsKey(context)) { |
| |
| } else { |
| |
| } |
| } |
| } |
| |
| if (context == null) { |
| context = new ContextData(null, null, taxonomy.getType()); |
| } |
| |
| if (!this.contextMap.containsKey(context)) { |
| |
| final String startDateStr = timeFormat.format(this.startDate); |
| final String endDateStr = timeFormat.format(this.endDate); |
| |
| final String contextRefID = (context.getPeriodType() == 0) ? ("As_Of_" + endDateStr + (this.instantScenarioCounter++)) |
| : ("Duration_" + startDateStr + "_To_" + endDateStr + (this.durationScenarioCounter++)); |
| |
| this.contextMap.put(context, contextRefID); |
| } |
| |
| xmlElement.addAttribute("contextRef", this.contextMap.get(context)); |
| xmlElement.addAttribute("unitRef", getUnitRef(taxonomy)); |
| xmlElement.addAttribute("decimals", getNumberOfDecimalPlaces(value).toString()); |
| |
| // add the child |
| xmlElement.addText(value.toPlainString()); |
| |
| return xmlElement; |
| } |
| |
| private String getUnitRef(final MixTaxonomyData tx) { |
| return tx.isPortfolio() ? UNITID_PURE : UNITID_CUR; |
| } |
| |
| /** |
| * Adds the generic number unit |
| */ |
| void addNumberUnit() { |
| final Element numerUnit = this.root.addElement("unit"); |
| numerUnit.addAttribute("id", UNITID_PURE); |
| final Element measure = numerUnit.addElement("measure"); |
| measure.addText("xbrli:pure"); |
| |
| } |
| |
| /** |
| * Adds the currency unit to the document |
| * |
| * @param currencyCode |
| */ |
| public void addCurrencyUnit(final String currencyCode) { |
| final Element currencyUnitElement = this.root.addElement("unit"); |
| currencyUnitElement.addAttribute("id", UNITID_CUR); |
| final Element measure = currencyUnitElement.addElement("measure"); |
| measure.addText("iso4217:" + currencyCode); |
| |
| } |
| |
| public void addContexts() { |
| final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); |
| for (final Entry<ContextData, String> entry : this.contextMap.entrySet()) { |
| final ContextData context = entry.getKey(); |
| final Element contextElement = this.root.addElement("context"); |
| contextElement.addAttribute("id", entry.getValue()); |
| contextElement.addElement("entity").addElement("identifier").addAttribute("scheme", SCHEME_URL).addText(IDENTIFIER); |
| |
| final Element periodElement = contextElement.addElement("period"); |
| |
| if (context.getPeriodType() == 0) { |
| periodElement.addElement("instant").addText(format.format(this.endDate)); |
| } else { |
| periodElement.addElement("startDate").addText(format.format(this.startDate)); |
| periodElement.addElement("endDate").addText(format.format(this.endDate)); |
| } |
| |
| final String dimension = context.getDimension(); |
| final String dimType = context.getDimensionType(); |
| if (dimType != null && dimension != null) { |
| contextElement.addElement("scenario").addElement("explicitMember").addAttribute("dimension", dimType).addText(dimension); |
| } |
| } |
| |
| } |
| |
| private Integer getNumberOfDecimalPlaces(final BigDecimal bigDecimal) { |
| final String string = bigDecimal.stripTrailingZeros().toPlainString(); |
| final int index = string.indexOf("."); |
| return index < 0 ? 0 : string.length() - index - 1; |
| } |
| } |