All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.functions.Parts Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.functions;


import net.sf.saxon.expr.Callable;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.StaticProperty;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.ma.map.DictionaryMap;
import net.sf.saxon.ma.map.MapItem;
import net.sf.saxon.ma.map.MapType;
import net.sf.saxon.ma.map.RecordTest;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.om.SequenceTool;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.tree.iter.EmptyIterator;
import net.sf.saxon.tree.iter.ListIterator;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.type.AtomicType;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.ValidationException;
import net.sf.saxon.value.*;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.function.Function;

/**
 * Proposed XPath 4.0 function that returns the components of an atomic value as a map (for example, the components
 * of a dateTime, a duration, or a URI).
 */
public class Parts extends SystemFunction implements Callable {

    /**
     * Get the return type, given knowledge of the actual arguments
     *
     * @param args the actual arguments supplied
     * @return the best available item type that the function will return
     */

    public ItemType getResultItemType(Expression[] args) {
        AtomicType argType = args[0].getStaticType().getPrimaryType().getAtomizedItemType().getPrimitiveItemType();
        if (argType == BuiltInAtomicType.ANY_ATOMIC) {
            return super.getResultItemType(args);
        }
        while (argType.getBaseType() != BuiltInAtomicType.ANY_ATOMIC) {
            // Handle xs:integer, xs:dayTimeDuration, xs:yearMonthDuration
            argType = (AtomicType)argType.getBaseType();
        }
        RecordTest resultType = resultTypeMap.get(argType);
        if (resultType == null) {
            return super.getResultItemType(args);
        } else {
            return resultType;
        }
    }

    @Override
    public Sequence call(XPathContext context, Sequence[] args) throws XPathException {
        DictionaryMap result = new DictionaryMap();
        AtomicValue subject = (AtomicValue)args[0].head();
        AtomicType type = subject.getPrimitiveType();
        if (type.getBaseType() == BuiltInAtomicType.DURATION) {
            type = BuiltInAtomicType.DURATION;
        }
        Map details = componentMap.get(type);
        if (details == null) {
            details = new HashMap<>();
        }
        for (Map.Entry component : details.entrySet()) {
            result.initialPut(component.getKey(), SequenceTool.toGroundedValue(component.getValue().supplier.apply(subject)));
        }
        result.initialPut("value", subject);
        return result;
    }

    private static class ComponentDetails {
        SequenceType componentType;
        Function supplier;
    }

    private final static Map> componentMap = new HashMap<>();

    private static void register(BuiltInAtomicType type, String fieldName, SequenceType componentType,
                                 Function supplier) {
        //noinspection Convert2Diamond
        Map typeData =
                componentMap.computeIfAbsent(type, k -> new HashMap());
        ComponentDetails details = new ComponentDetails();
        details.componentType = componentType;
        details.supplier = supplier;
        typeData.put(fieldName, details);
    }

    static SequenceIterator oneInt(long x) {
        return SingletonIterator.makeIterator(Int64Value.makeIntegerValue(x));
    }

    static SequenceIterator oneDec(BigDecimal x) {
        return SingletonIterator.makeIterator(new BigDecimalValue(x));
    }

    static SequenceIterator timezone(CalendarValue a) {
        if (a.hasTimezone()) {
            try {
                return SingletonIterator.makeIterator(
                        DayTimeDurationValue.fromMilliseconds(60000L * a.getTimezoneInMinutes()));
            } catch (ValidationException e) {
                return EmptyIterator.ofAtomic();
            }
        } else {
            return EmptyIterator.ofAtomic();
        }
    }


    static {
        register(BuiltInAtomicType.DATE_TIME, "year", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((DateTimeValue) a).getYear())
        );
        register(BuiltInAtomicType.DATE_TIME, "month", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((DateTimeValue) a).getMonth())
        );
        register(BuiltInAtomicType.DATE_TIME, "day", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((DateTimeValue) a).getDay())
        );
        register(BuiltInAtomicType.DATE_TIME, "hours", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((DateTimeValue) a).getHour())
        );
        register(BuiltInAtomicType.DATE_TIME, "minutes", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((DateTimeValue) a).getMinute())
        );
        register(BuiltInAtomicType.DATE_TIME, "seconds", SequenceType.SINGLE_INTEGER,
                 a -> {
                     BigDecimal d = BigDecimal.valueOf(((DateTimeValue)a).getNanosecond());
                     d = d.divide(BigDecimalValue.BIG_DECIMAL_ONE_BILLION, 6, RoundingMode.HALF_UP);
                     d = d.add(BigDecimal.valueOf(((DateTimeValue) a).getSecond()));
                     return oneDec(d);
                 }
        );
        register(BuiltInAtomicType.DATE_TIME, "timezone", SequenceType.OPTIONAL_DAY_TIME_DURATION,
                 a -> timezone((DateTimeValue) a)
        );

        register(BuiltInAtomicType.DATE, "year", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((DateValue) a).getYear())
        );
        register(BuiltInAtomicType.DATE, "month", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((DateValue) a).getMonth())
        );
        register(BuiltInAtomicType.DATE, "day", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((DateValue) a).getDay())
        );
        register(BuiltInAtomicType.DATE, "timezone", SequenceType.OPTIONAL_DAY_TIME_DURATION,
                 a -> timezone((DateValue) a)
        );

        register(BuiltInAtomicType.TIME, "hours", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((TimeValue) a).getHour())
        );
        register(BuiltInAtomicType.TIME, "minutes", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((TimeValue) a).getMinute())
        );
        register(BuiltInAtomicType.TIME, "seconds", SequenceType.SINGLE_INTEGER,
                 a -> {
                     BigDecimal d = BigDecimal.valueOf(((TimeValue) a).getNanosecond());
                     d = d.divide(BigDecimalValue.BIG_DECIMAL_ONE_BILLION, 6, RoundingMode.HALF_UP);
                     d = d.add(BigDecimal.valueOf(((TimeValue) a).getSecond()));
                     return oneDec(d);
                 }
        );
        register(BuiltInAtomicType.TIME, "timezone", SequenceType.OPTIONAL_DAY_TIME_DURATION,
                 a -> timezone((TimeValue) a)
        );

        register(BuiltInAtomicType.G_YEAR, "year", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((GDateValue) a).getYear())
        );
        register(BuiltInAtomicType.G_YEAR, "timezone", SequenceType.OPTIONAL_DAY_TIME_DURATION,
                 a -> timezone((GDateValue) a)
        );

        register(BuiltInAtomicType.G_YEAR_MONTH, "year", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((GDateValue) a).getYear())
        );
        register(BuiltInAtomicType.G_YEAR_MONTH, "month", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((GDateValue) a).getMonth())
        );
        register(BuiltInAtomicType.G_YEAR_MONTH, "timezone", SequenceType.OPTIONAL_DAY_TIME_DURATION,
                 a -> timezone((GDateValue) a)
        );

        register(BuiltInAtomicType.G_MONTH, "month", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((GDateValue) a).getMonth())
        );
        register(BuiltInAtomicType.G_MONTH, "timezone", SequenceType.OPTIONAL_DAY_TIME_DURATION,
                 a -> timezone((GDateValue) a)
        );

        register(BuiltInAtomicType.G_MONTH_DAY, "month", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((GDateValue) a).getMonth())
        );
        register(BuiltInAtomicType.G_MONTH_DAY, "day", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((GDateValue) a).getDay())
        );
        register(BuiltInAtomicType.G_MONTH_DAY, "timezone", SequenceType.OPTIONAL_DAY_TIME_DURATION,
                 a -> timezone((GDateValue) a)
        );

        register(BuiltInAtomicType.G_DAY, "day", SequenceType.SINGLE_INTEGER,
                 a -> oneInt(((GDateValue) a).getDay())
        );
        register(BuiltInAtomicType.G_DAY, "timezone", SequenceType.OPTIONAL_DAY_TIME_DURATION,
                 a -> timezone((GDateValue) a)
        );

        register(BuiltInAtomicType.DURATION, "years", SequenceType.SINGLE_INTEGER,
                 a -> oneInt((long) ((DurationValue) a).getYears() * ((DurationValue) a).signum())
        );
        register(BuiltInAtomicType.DURATION, "months", SequenceType.SINGLE_INTEGER,
                 a -> oneInt((long) ((DurationValue) a).getMonths() * ((DurationValue) a).signum())
        );
        register(BuiltInAtomicType.DURATION, "days", SequenceType.SINGLE_INTEGER,
                 a -> oneInt((long) ((DurationValue) a).getDays() * ((DurationValue) a).signum())
        );
        register(BuiltInAtomicType.DURATION, "hours", SequenceType.SINGLE_INTEGER,
                 a -> oneInt((long) ((DurationValue) a).getHours() * ((DurationValue) a).signum())
        );
        register(BuiltInAtomicType.DURATION, "minutes", SequenceType.SINGLE_INTEGER,
                 a -> oneInt((long) ((DurationValue) a).getMinutes() * ((DurationValue) a).signum())
        );
        register(BuiltInAtomicType.DURATION, "seconds", SequenceType.SINGLE_INTEGER,
                 a -> SingletonIterator.makeIterator(((DurationValue) a).getComponent(AccessorFn.Component.SECONDS))
        );

        register(BuiltInAtomicType.HEX_BINARY, "octets", SequenceType.makeSequenceType(BuiltInAtomicType.UNSIGNED_BYTE, StaticProperty.ALLOWS_ZERO_OR_MORE),
                 a -> {
                     List result = new ArrayList<>();
                     for (byte b : ((HexBinaryValue)a).getBinaryValue()) {
                         result.add(new Int64Value(b & 0xFF, BuiltInAtomicType.UNSIGNED_BYTE));
                     }
                     return new ListIterator.Of<>(result);
                 }
        );

        register(BuiltInAtomicType.BASE64_BINARY, "octets", SequenceType.makeSequenceType(BuiltInAtomicType.UNSIGNED_BYTE, StaticProperty.ALLOWS_ZERO_OR_MORE),
                 a -> {
                     List result = new ArrayList<>();
                     for (byte b : ((Base64BinaryValue) a).getBinaryValue()) {
                         result.add(new Int64Value(b & 0xFF, BuiltInAtomicType.UNSIGNED_BYTE));
                     }
                     return new ListIterator.Of<>(result);
                 }
        );

        register(BuiltInAtomicType.QNAME, "prefix", SequenceType.OPTIONAL_STRING,
                 a -> {
                    String prefix = ((QNameValue) a).getStructuredQName().getPrefix();
                    return prefix.isEmpty()
                            ? EmptyIterator.ofAtomic()
                            : SingletonIterator.makeIterator(new StringValue(prefix, BuiltInAtomicType.NCNAME));
                 }
        );
        register(BuiltInAtomicType.QNAME, "namespace-uri", SequenceType.OPTIONAL_STRING,
                 a -> {
                     String uri = ((QNameValue) a).getStructuredQName().getURI();
                     return uri.isEmpty()
                             ? EmptyIterator.ofAtomic()
                             : SingletonIterator.makeIterator(new StringValue(uri, BuiltInAtomicType.ANY_URI));
                 }
        );
        register(BuiltInAtomicType.QNAME, "local-name", SequenceType.SINGLE_STRING,
                 a -> {
                     String local = ((QNameValue) a).getStructuredQName().getLocalPart();
                     return SingletonIterator.makeIterator(new StringValue(local, BuiltInAtomicType.NCNAME));
                 }
        );
        register(BuiltInAtomicType.ANY_URI, "scheme", SequenceType.OPTIONAL_STRING,
                 a -> {
                     MapItem map = uriComponents(a.getStringValue());
                     return SingletonIterator.makeIterator((StringValue)map.get(new StringValue("scheme")));
                 }
        );
        register(BuiltInAtomicType.ANY_URI, "authority", SequenceType.OPTIONAL_STRING,
                 a -> {
                     MapItem map = uriComponents(a.getStringValue());
                     return SingletonIterator.makeIterator((StringValue) map.get(new StringValue("authority")));
                 }
        );
        register(BuiltInAtomicType.ANY_URI, "path", SequenceType.OPTIONAL_STRING,
                 a -> {
                     MapItem map = uriComponents(a.getStringValue());
                     return SingletonIterator.makeIterator((StringValue) map.get(new StringValue("path")));
                 }
        );
        register(BuiltInAtomicType.ANY_URI, "query", SequenceType.OPTIONAL_STRING,
                 a -> {
                     MapItem map = uriComponents(a.getStringValue());
                     return SingletonIterator.makeIterator((StringValue) map.get(new StringValue("query")));
                 }
        );
        register(BuiltInAtomicType.ANY_URI, "fragment", SequenceType.OPTIONAL_STRING,
                 a -> {
                     MapItem map = uriComponents(a.getStringValue());
                     return SingletonIterator.makeIterator((StringValue) map.get(new StringValue("fragment")));
                 }
        );
        register(BuiltInAtomicType.ANY_URI, "query-parameters", MapType.SINGLE_MAP_ITEM,
                 a -> {
                     MapItem map = uriComponents(a.getStringValue());
                     return SingletonIterator.makeIterator((MapItem) map.get(new StringValue("queryParams")));
                 }
        );

        buildRecordTypes();

    }

    private static Map resultTypeMap;

    private static void buildRecordTypes() {
        resultTypeMap = new HashMap<>();
        for (Map.Entry> entry : componentMap.entrySet()) {
             //RecordTest(List names, List types, List optionalFieldNames, boolean extensible)
            List names = new ArrayList<>();
            List types = new ArrayList<>();
            List optionalFields = new ArrayList<>();
            for (Map.Entry details : entry.getValue().entrySet()) {
                names.add(details.getKey());
                types.add(details.getValue().componentType);
                if (Cardinality.allowsZero(details.getValue().componentType.getCardinality())) {
                    optionalFields.add(details.getKey());
                }
            }
            names.add("value");
            types.add(SequenceType.makeSequenceType(entry.getKey(), StaticProperty.EXACTLY_ONE));
            resultTypeMap.put(entry.getKey(), new RecordTest(names, types, optionalFields, false));
        }
    }

    public static SequenceType getComponentType(BuiltInAtomicType type, String componentName) {
        if (type == BuiltInAtomicType.DAY_TIME_DURATION || type == BuiltInAtomicType.YEAR_MONTH_DURATION) {
            type = BuiltInAtomicType.DURATION;
        }
        Map typeData = componentMap.get(type);
        if (typeData == null) {
            return null;
        } else {
            ComponentDetails details = typeData.get(componentName);
            return details == null ? null : details.componentType;
        }
    }

    public static SequenceIterator getComponentValue(AtomicValue value, String componentName) {
        AtomicType type = value.getPrimitiveType();
        if (type == BuiltInAtomicType.DAY_TIME_DURATION || type == BuiltInAtomicType.YEAR_MONTH_DURATION) {
            type = BuiltInAtomicType.DURATION;
        }
        Map typeData = componentMap.get(type);
        if (typeData == null) {
            return EmptyIterator.ofAtomic();
        }
        Function supplier = typeData.get(componentName).supplier;
        return supplier.apply(value);
    }

    public static DictionaryMap uriComponents(String arg) {
        DictionaryMap dict = new DictionaryMap();
        try {
            URI uri = new URI(arg);
            dict.initialPut("isValid", BooleanValue.TRUE);
            boolean absolute = uri.isAbsolute();
            dict.initialPut("isAbsolute", BooleanValue.get(absolute));
            dict.initialPut("isOpaque", BooleanValue.get(uri.isOpaque()));
            URI surrogateURI = absolute ? uri : getSurrogateURI(uri);
            String val = absolute ? uri.getAuthority() : null;
            if (val != null) {
                dict.initialPut("authority", new StringValue(val));
            }
            val = absolute ? uri.getRawAuthority() : null;
            if (val != null) {
                dict.initialPut("rawAuthority", new StringValue(val));
            }
            val = absolute ? uri.getUserInfo() : null;
            if (val != null) {
                dict.initialPut("userInfo", new StringValue(val));
            }
            val = absolute ? uri.getRawUserInfo() : null;
            if (val != null) {
                dict.initialPut("rawUserInfo", new StringValue(val));
            }
            val = absolute ? uri.getScheme() : null;
            if (val != null) {
                dict.initialPut("scheme", new StringValue(val));
            }
            val = absolute ? uri.getHost() : null;
            if (val != null) {
                dict.initialPut("host", new StringValue(val));
            }
            val = surrogateURI.getSchemeSpecificPart();
            if (val != null) {
                dict.initialPut("schemeSpecificPart", new StringValue(val));
            }
            val = surrogateURI.getRawSchemeSpecificPart();
            if (val != null) {
                dict.initialPut("rawSchemeSpecificPart", new StringValue(val));
            }
            if (absolute) {
                int port = uri.getPort();
                if (port >= 0) {
                    dict.initialPut("port", new Int64Value(port));
                }
            }
            val = surrogateURI.getPath();
            if (val != null) {
                if (val.equals("/") && !arg.endsWith("/")) {
                    // Relevant for .NET, where the absolute path of a URI such as "http://www.example.com" is wrongly
                    // given as "/" rather than "". This doesn't catch all cases, for example if there is a query part.
                    val = "";
                }
                if (!absolute && val.startsWith("/") && !arg.startsWith("/")) {
                    // For a relative URI such as "books.xml", return "books.xml" rather than "/books.xml"
                    val = val.substring(1);
                }
                dict.initialPut("path", new StringValue(val));
            }
            val = surrogateURI.getRawPath();
            if (val != null) {
                dict.initialPut("rawPath", new StringValue(val));
            }
            val = surrogateURI.getRawQuery();
            if (val != null) {
                dict.initialPut("rawQuery", new StringValue(val));
            }
            val = surrogateURI.getQuery();
            if (val != null) {
                dict.initialPut("query", new StringValue(val));
                DictionaryMap params = new DictionaryMap();
                StringTokenizer t = new StringTokenizer(val, ";&");
                while (t.hasMoreTokens()) {
                    String tok = t.nextToken();
                    int eq = tok.indexOf('=');
                    if (eq >= 0) {
                        String keyword = tok.substring(0, eq);
                        String value = tok.substring(eq + 1);
                        params.initialAppend(keyword, new StringValue(value));
                    } else {
                        params.initialAppend(tok, BooleanValue.TRUE);
                    }
                }
                dict.initialPut("queryParams", params);
            }
            val = surrogateURI.getRawFragment();
            if (val != null) {
                dict.initialPut("rawFragment", new StringValue(val));
            }
            val = surrogateURI.getFragment();
            if (val != null) {
                dict.initialPut("fragment", new StringValue(val));
            }
            val = uri.toASCIIString();
            if (val != null) {
                dict.initialPut("asciiString", new StringValue(val));
            }
            return dict;
        } catch (URISyntaxException e) {
            dict.initialPut("isValid", BooleanValue.FALSE);
            dict.initialPut("error", new StringValue(e.getMessage()));
            return dict;
        }
    }

    /**
     * For a relative URI, get a corresponding URI whose components are accessible. No action on Java;
     * on C#, resolve the relative URI against an arbitrary base URI
     */

    @CSharpReplaceBody(code="return new System.Uri(new System.Uri(\"http://dummy.uri/\"), relative);")
    private static URI getSurrogateURI(URI relative) {
        return relative;
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy