0
votes

I am trying to implement custom function using Saxon as defined here-> https://specifications.xbrl.org/registries/functions-registry-1.0/80132%20xfi.identifier/80132%20xfi.identifier%20function.html

public class IdentifierFunction implements ExtensionFunction {

    public QName getName() {
        return new QName("http://www.xbrl.org/2005/function/instance", "identifier");
    }

    public SequenceType getResultType() {
        return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE);
    }

    public net.sf.saxon.s9api.SequenceType[] getArgumentTypes() {
        return new SequenceType[] { SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE) };
    }

    public XdmValue call(XdmValue[] arguments) throws SaxonApiException {
        String arg = ((XdmAtomicValue) arguments[0].itemAt(0)).getStringValue();
        String newExpression="(//xbrli:xbrl/xbrli:context[@id=("+arg+"/@contextRef"+")])[1]/xbrli:entity/xbrli:identifier";
        String nodeString=this.getxPathResolver().resolveNode(this.getXbrl(),newExpression);
        return new XdmAtomicValue(nodeString);
    }
}

resolveNode() is above code is implemented as follows

public String resolveNode(byte[] xbrlBytes, String expressionValue) {
        // 1. Instantiate an XPathFactory.
        javax.xml.xpath.XPathFactory factory = new XPathFactoryImpl();

        // 2. Use the XPathFactory to create a new XPath object
        javax.xml.xpath.XPath xpath = factory.newXPath();

        NamespaceContext ctx = new NamespaceContext() {
            @Override
            public String getNamespaceURI(String aPrefix) {
                if (aPrefix.equals("xfi"))
                    return "http://www.xbrl.org/2005/function/instance";
                else if (aPrefix.equals("xs"))
                    return "http://www.w3.org/2001/XMLSchema";
                else if (aPrefix.equals("xbrli"))
                    return "http://www.xbrl.org/2003/instance";
                else
                    return null;
            }

            @Override
            public Iterator getPrefixes(String val) {
                throw new UnsupportedOperationException();
            }

            @Override
            public String getPrefix(String uri) {
                throw new UnsupportedOperationException();
            }
        };
        xpath.setNamespaceContext(ctx);
        try {
            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
            Document someXML = documentBuilder.parse(new InputSource(new StringReader(new String(xbrlBytes))));

            // 3. Compile an XPath string into an XPathExpression
            javax.xml.xpath.XPathExpression expression = xpath.compile(expressionValue);
            Object result = expression.evaluate(someXML, XPathConstants.NODE);
            // 4. Evaluate the XPath expression on an input document
            Node nodes = (Node) result;
            return nodeToString(nodes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

When I evaluate xfi:identifier(args) , i get String like below:

<xbrli:identifier xmlns:xbrli="http://www.xbrl.org/2003/instance"
                  xmlns:iso4217="http://www.xbrl.org/2003/iso4217"
                  xmlns:jenv-bw2-dim="http://www.nltaxonomie.nl/nt13/jenv/20181212/dictionary/jenv-bw2-axes"
                  xmlns:jenv-bw2-dm="http://www.nltaxonomie.nl/nt13/jenv/20181212/dictionary/jenv-bw2-domains"
                  xmlns:jenv-bw2-i="http://www.nltaxonomie.nl/nt13/jenv/20181212/dictionary/jenv-bw2-data"
                  xmlns:kvk-i="http://www.nltaxonomie.nl/nt13/kvk/20181212/dictionary/kvk-data"
                  xmlns:link="http://www.xbrl.org/2003/linkbase"
                  xmlns:nl-cd="http://www.nltaxonomie.nl/nt13/sbr/20180301/dictionary/nl-common-data"
                  xmlns:rj-i="http://www.nltaxonomie.nl/nt13/rj/20181212/dictionary/rj-data"
                  xmlns:rj-t="http://www.nltaxonomie.nl/nt13/rj/20181212/dictionary/rj-tuples"
                  xmlns:xbrldi="http://xbrl.org/2006/xbrldi"
                  xmlns:xlink="http://www.w3.org/1999/xlink"
                  scheme="http://www.kvk.nl/kvk-id">62394207</xbrli:identifier>

However, I want to evaluate function number(xfi:identifier(args))

This results in NaN which is obvious because complete node string cannot be converted to number. I think, I need to change my function so that it returns Node. However, I am not sure how to do that. I tried google and also looked at Saxon documentation, but no luck yet. Can someone help me? Basically, custom function should return an element node as per definition. and when I use number(xfi:identifier) it should give me 62394207 in this case.

regards,

Venky

1

1 Answers

0
votes

Firstly, the XBRL spec for the function seems to imply that the function expects a node as argument and returns a node as its result, but in your implementation getArgumentTypes() and getResultType() define the type as xs:string - so this needs to change.

And the function should return an XdmNode, which is a subclass of XdmValue.

Next, it's very inefficient to be creating a DocumentBuilderFactory and XPathFactory, constructing an XML document tree, and compiling an XPath expression, every time your function is executed. I strongly suspect none of this is necessary.

Instead of having this.getXbrl() return a raw lexical document as byte[], have it return a prebuilt XdmNode representing the document tree. And then I would suggest that rather than selecting within that tree using XPath, you use Saxon's linq-like navigation API. If this XdmNode is in variable "root", then the XPath expression

//xbrli:xbrl/xbrli:context[@id=("+arg+"/@contextRef"+")

translates into something like

root.select(descendant("xbrl").then(child("context)).where(attributeEq("id", arg))

(except that I'm not quite sure what you're passing as arg to make your XPath expression make sense).

But you can use XPath if you prefer; just use Saxon's s9api interfaces for XPath and make sure the XPath expression is only compiled once and used repeatedly. It's straightforward then to get an XdmNode as the result of your XPath expression, which can be returned directly as the result of your extension function.