summaryrefslogtreecommitdiff
path: root/support/texosquery
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/texosquery
Initial commit
Diffstat (limited to 'support/texosquery')
-rw-r--r--support/texosquery/CHANGES184
-rw-r--r--support/texosquery/README.md413
-rw-r--r--support/texosquery/java/FileListType.java24
-rw-r--r--support/texosquery/java/FilePathSortComparator.java186
-rw-r--r--support/texosquery/java/FileSortComparator.java170
-rw-r--r--support/texosquery/java/FileSortType.java131
-rw-r--r--support/texosquery/java/FileWalkVisitor.java221
-rw-r--r--support/texosquery/java/Manifest-jre5.txt2
-rw-r--r--support/texosquery/java/Manifest-jre7.txt2
-rw-r--r--support/texosquery/java/Manifest-jre8.txt2
-rw-r--r--support/texosquery/java/QueryAction.java448
-rw-r--r--support/texosquery/java/QueryActionType.java19
-rw-r--r--support/texosquery/java/TeXOSQuery.java4805
-rw-r--r--support/texosquery/java/TeXOSQueryJRE5.java54
-rw-r--r--support/texosquery/java/TeXOSQueryJRE7.java197
-rw-r--r--support/texosquery/java/TeXOSQueryJRE8.java384
-rw-r--r--support/texosquery/texosquery.dtx8136
-rw-r--r--support/texosquery/texosquery.ins98
-rw-r--r--support/texosquery/texosquery.pdfbin0 -> 505685 bytes
19 files changed, 15476 insertions, 0 deletions
diff --git a/support/texosquery/CHANGES b/support/texosquery/CHANGES
new file mode 100644
index 0000000000..90a16eff9f
--- /dev/null
+++ b/support/texosquery/CHANGES
@@ -0,0 +1,184 @@
+1.6 (2017/06/20):
+
+ * Added new command line options:
+ --encoding (-enc)
+ --default-encoding (-defenc)
+
+ * Added new command line action:
+ --codeset (-cs)
+
+1.5 (2017/05/23):
+
+ * Added new command line options:
+ --strip-path-prefix (-sp)
+ --nostrip-path-prefix
+ --strip-uri-prefix (-su)
+ --nostrip-uri-prefix
+ --replace-path (-rp)
+ --noreplace-path
+ --replace-uri (-ru)
+ --noreplace-uri
+
+ * Bash scripts now check for cygwin
+
+1.4 (2017/05/05):
+
+ * The fallback value for 'openin_any' for texosquery-jre8
+ is now 'a' if unset.
+
+ * Added clearer instructions in texosquery.cfg and information
+ messages to the transcript.
+
+ * Added \TeXOSQueryFromFile.
+
+ * Shortcut commands only use quotes to delimit arguments
+ in the unrestricted mode.
+
+1.3 (2017/03/31):
+
+ * If 'openin_any' hasn't been set, the fallback value for
+ texosquery.jar and texosquery-jre5.jar is now 'a'
+ (instead of 'p'). Fallback value for texosquery-jre8.jar
+ is still 'p'.
+
+ * Fixed spaces in path issue for the Windows .bat files.
+ (https://github.com/nlct/texosquery/issues/2)
+
+1.2.1 (2017/03/28):
+
+ * Minor bug fix in texosquery.tex (fixed decimal separator
+ for numeric patterns with all # in the fractional part)
+
+1.2 (2017/03/23):
+
+ * Now has three .jar files depending on the Java installation:
+
+ - texosquery-jre8.jar
+
+ Full application. May be used with the CLDR locale provider
+ (Unicode Consortium's Common Locale Data Repository).
+ Requires at least Java 8 (use with
+ java -Djava.locale.providers=CLDR,JRE or
+ add "java.locale.providers=CLDR,JRE" to the
+ JAVA_TOOLS_OPTIONS environment variable to access the CLDR)
+
+ - texosquery.jar
+
+ The default version of the application. Can't be used with
+ CLDR. Less locale support. Requires at least Java 7.
+
+ - texosquery-jre5.jar
+
+ Cut-down version of the application with significantly less
+ locale support. Provided for old systems that can't upgrade,
+ but not recommended. (Use of Java 5 or 6 is deprecated.)
+ Requires at least Java 5.
+
+ * Corresponding scripts that run the appropriate .jar file
+ are bundled in texosquery.dtx and extracted using:
+
+ tex texosquery.ins
+
+ - Unix-like (run `chmod +x texosquery*.sh`):
+
+ texosquery-jre8.sh : runs texosquery-jre8.jar with
+ -Djava.locale.providers=CLDR,JRE
+
+ texosquery.sh : runs texosquery.jar
+
+ texosquery-jre5.sh : runs texosquery-jre5.jar
+
+ (I recommend the removal of the '.sh' extension
+ for more convenient invocation.)
+
+ - Windows (rename texosquery*.batch to texosquery*.bat):
+
+ texosquery-jre8.bat : runs texosquery-jre8.jar with
+ -Djava.locale.providers=CLDR,JRE
+
+ texosquery.bat : runs texosquery.jar
+
+ texosquery-jre5.bat : runs texosquery-jre5.jar
+
+ You can edit these files if additional Java settings
+ are required (e.g. -Dfile.encoding=UTF-8). Alternatively
+ set the JAVA_TOOLS_OPTIONS environment variable.
+
+ * New texosquery.cfg configuration file. Edit the definition
+ of \TeXOSInvokerName to indicate which application you
+ need to use. For example, if Java 8 is installed:
+
+ \def\TeXOSInvokerName{texosquery-jre8}
+
+ See the installation section of the manual.
+
+ * New command line switches:
+
+ Actions:
+
+ --bcp47 / -b
+ --codeset-lcs / -C
+ --locale-data / -D
+ --numeric / -N
+ --date-time / -M
+ --time-zones / -Z
+ --list-dir / -ld
+ --list-regular / -lr
+ --filterlist-dir / -fd
+ --filterlist-regular / -fr
+ --walk / -w
+
+ Options:
+
+ --debug [⟨n⟩]
+ --nodebug
+ --compatible ⟨n⟩ / -compat ⟨n⟩
+
+ * Actions --list (-i) and --filterlist (-f) now have an
+ optional argument to indicate the sort order.
+
+ * When accessing file information, the TEXMF openin_any
+ setting is first checked to determine if read access is
+ permitted.
+
+ * Returned values now include some shorthand markup that's
+ internally converted when read in by \TeXOSQuery. For
+ example "texosquery -n" now returns
+
+ \pdfd \fcln 20161113161156\fpls 00\fcsq 00\fcsq
+
+ instead of
+
+ D:20161113161156+00'00'
+
+ To restore the original behaviour use --compatible 1
+
+ See the manual for further details.
+
+1.1 (2016-07-14):
+
+ * texosquery.jar:
+
+ - Added --dirname (-e)
+
+ - Multiline results add braces to each line
+ (to make it easier to parse the results in TeX).
+
+ - Code refactored.
+
+ - Code now compiled for Java 5 to support users with
+ old versions of the JRE.
+
+ * texosquery.tex:
+
+ - \TeXOSQueryNow and \TeXOSQueryFileDate set the category
+ code of D to 12 (to match \pdfcreationdate).
+
+ - new command \TeXOSQueryDirName
+
+ - dry run mode is now by default on unless shell escape=1
+ (unrestricted mode)
+
+1.0 (2016-07-08):
+
+ * Initial release
diff --git a/support/texosquery/README.md b/support/texosquery/README.md
new file mode 100644
index 0000000000..ce37682edd
--- /dev/null
+++ b/support/texosquery/README.md
@@ -0,0 +1,413 @@
+# texosquery
+Cross-platform Java application to query OS information designed for use in
+TeX's shell escape mechanism.
+
+The application can query the following:
+
+ - locale information
+ - default file encoding
+ - current working directory
+ - user home directory
+ - temporary directory
+ - OS name, arch and version
+ - Current date and time in PDF format
+ (for TeX formats that don't provide `\pdfcreationdate`)
+ - Date-time stamp of a file in PDF format
+ (for TeX formats that don't provide `\pdffilemoddate`)
+ - Size of a file in bytes
+ (for TeX formats that don't provide `\pdffilesize`)
+ - Contents of a directory (captured as a list)
+ - Directory contents filtered by regular expression
+ (captured as a list)
+ - URI of a file
+ - Canonical path of a file
+
+All paths use a forward slash as directory divider so results
+can be used, for example, in commands like `\includegraphics`.
+
+There are files provided for easy access in (La)TeX documents:
+
+ - `texosquery.tex` : generic TeX code
+ - `texosquery.sty` : LaTeX package
+
+This provides the command `\TeXOSQuery` to run `texosquery` using
+TeX's shell escape mechanism and capture the result in a control
+sequence. The category code of most of TeX's default special
+characters (and some other potentially problematic characters) is
+temporarily changed to 12 while reading the result. Some of the
+`texosquery` output contains short markup commands (such as `\wrp`)
+which are internally converted within `\TeXOSQuery`.
+
+## Important Notes
+
+The TeX code uses a piped shell escape to capture the result from
+`texosquery`. This means that you must have the piped shell escape
+enabled to make use of this feature. MiKTeX users need the
+`--enable-pipes` option on the TeX command line to enable this.
+The alternative is to run `texosquery` outside of TeX and
+capture the output in a temporary file, which can then be
+read using `\TeXOSQueryFromFile`.
+
+You need to correctly set up the configuration file `texosquery.cfg`
+to match your system. The TeX package managers can't do this for you
+automatically. Copy the file to either your `TEXMFHOME` or
+`TEXMFLOCAL` tree to prevent it from being overwritten by subsequent
+updates.
+
+`texosquery-jre8` is on the restricted list for TeX Live 2017. To
+take advantage of this, you must have at least Java 8 installed and
+edit the `texosquery.cfg` file to:
+```tex
+\def\TeXOSInvokerName{texosquery-jre8}
+\TeXOSQueryAllowRestricted
+```
+(Note that the second line above has been uncommented.)
+
+Note that due to TeX's security measures, quotes can't be used in the
+restricted mode's shell escape. This means that if you need to pass
+a file name containing a space as an argument to `texosquery`,
+you'll have to use the unrestricted mode.
+
+## Installation
+
+Installation is best done using your TeX package manager.
+Manual installation instructions are described below.
+For more detail, see the documentation.
+
+### Compiling the Documentation
+
+To compile the documentation:
+```bash
+pdflatex texosquery.dtx
+makeglossaries texosquery
+makeindex -s gglo.ist -t texosquery.glg -o texosquery.gls texosquery.glo
+pdflatex texosquery.dtx
+makeindex -s gind.ist texosquery.idx
+pdflatex texosquery.dtx
+pdflatex texosquery.dtx
+```
+
+### Extracting the Files
+
+Except for the `.jar` files, the `texosquery` files are all
+bundled inside `texosquery.dtx` with the extraction commands
+provided in `texosquery.ins`. To extract all the files do:
+```bash
+tex texosquery.ins
+```
+
+The `.sh` files are bash scripts for Unix-like users. These will
+need to be set to executable using `chmod`. I recommend
+that you also remove the `.sh` extension. Windows users can
+deleted these files.
+
+The `.batch` files are batch scripts for Windows users. Unix-like
+users can delete these files. These files are given the extension
+`.batch` as TeX on Windows doesn't allow the creation of `.bat`
+files. The extension will need to be changed to `.bat` after the
+files have been extracted.
+
+### Installing the Application
+
+In the following, replace _TEXMF_ with the path to your TEXMF directory.
+You can find your home TEXMF directory using
+```bash
+kpsewhich -var-value=TEXMFHOME
+```
+
+There are now three Java applications provided by this package:
+
+ - `texosquery.jar` (requires at least Java 7)
+ - `texosquery-jre8.jar` (requires at least Java 8)
+ - `texosquery-jre5.jar` (requires at least Java 5)
+
+(See the ["Security"](#security) section below.)
+
+There are corresponding bash scripts for Unix-like users (the `.sh`
+extension added by `texosquery.ins` should be removed and the files
+made executable):
+
+ - `texosquery`
+ - `texosquery-jre8`
+ - `texosquery-jre5`
+
+There are also corresponding batch scripts for Windows users (the `.batch`
+extension created by the `.ins` file should be changed to `.bat`):
+
+ - `texosquery.bat`
+ - `texosquery-jre8.bat`
+ - `texosquery-jre5.bat`
+
+Each script uses `kpsewhich` to find the corresponding `.jar` file
+and runs it.
+
+Put the `.jar` files in _TEXMF_`/scripts/texosquery/` and the
+bash or `.bat` files somewhere on your path. You may or may not need
+to refresh the TeX database.
+
+To test the installation, run
+```bash
+texosquery -v
+```
+in the command prompt or terminal. (Also try with `texosquery-jre8`
+and `texosquery-jre5`.)
+
+If successful, it should show the version number.
+
+### Installing the TeX Code
+
+The `.tex`, `.cfg` and `.sty` files should all be extracted from `texosquery.dtx`
+with `tex texosquery.ins` as described above.
+
+ - Move `texosquery.tex` to _TEXMF_`/tex/generic/texosquery/`
+ - Move `texosquery.cfg` to _TEXMF_`/tex/generic/texosquery/`
+ - Move `texosquery.sty` to _TEXMF_`/tex/latex/texosquery/`
+ - Move `texosquery.pdf` to _TEXMF_`/doc/generic/texosquery/`
+
+The `texosquery.cfg` file allows you to specify which application you
+want to use. First check which version of the Java Runtime
+Environment (JRE) you have installed:
+```bash
+java -version
+```
+This should display the version information. (For example, `"1.8.0_92"`)
+
+If the version number starts with `1.8` ("Java 8"), then you can use `texosquery-jre8.jar`.
+This is the full application. You can use `texosquery` or
+`texosquery-jre5` as well, but there's less locale support with them.
+The `texosquery-jre8` bash script and `texosquery-jre8.bat` batch script invokes Java with
+`-Djava.locale.providers=CLDR,JRE`.
+
+If Windows users have simply been supplied with an executable versions
+of the `texosquery-jre8.jar` file (`texosquery-jre8.exe`) then you'll have to set the
+`JAVA_TOOL_OPTIONS` environment variable instead.
+
+This `java.locale.providers` setting allows Java to access locale
+information from the [Unicode Common Locale Data Repository
+(CLDR)](http://cldr.unicode.org/) installed on their system. The
+pending Java 9 should include this by default, so this will only be
+relevant to Java 8. Earlier versions of Java (7 or below) don't have
+this option, which limits the locale support to that which is
+natively provided by the JRE. Note that Java 7 and earlier versions
+[have reached their end of life](http://www.oracle.com/technetwork/java/eol-135779.html) and are now **deprecated**.
+
+If the version information starts with `1.7` ("Java 7"), then you can use
+`texosquery.jar`. This is the default application and provides most
+of the functions of the full application, but there's less locale support.
+You can also use the even more limited `texosquery-jre5`, but you can't use
+`texosquery-jre8`, so you can't take advantage of the CLDR.
+
+If the version information starts with `1.5` ("Java 5") or `1.6`
+("Java 6"), then you can _only_ use `texosquery-jre5`. The locale
+support is significantly reduced in this case and there's no support
+for language scripts.
+
+Once you have determined which application you want to use, edit the
+`texosquery.cfg` so that `\TeXOSInvokerName` is defined to the appropriate invocation. For example, with Java 8:
+```tex
+\def\TeXOSInvokerName{texosquery-jre8}
+```
+or with Java 5 or 6:
+```tex
+\def\TeXOSInvokerName{texosquery-jre5}
+```
+(bash users will have to add the `.sh` extension if it hasn't
+been removed, as per the above instructions.)
+
+## Examples:
+
+Plain TeX:
+
+```tex
+% arara: pdftex: {shell: on}
+\input texosquery
+
+\TeXOSQuery{\result}{-b}
+IEFT BCP 47 language tag: \result.
+
+\TeXOSQueryLocale{\result}
+POSIX locale: \result.
+
+\TeXOSQueryCwd{\result}
+cwd: {\tt \result}.
+
+\TeXOSQueryNow{\result}
+now: \result.
+
+\TeXOSQueryFileDate{\result}{\jobname.tex}
+file date: \result.
+
+\TeXOSQueryFileSize{\result}{\jobname.tex}
+file size: \result bytes.
+
+\bye
+```
+
+LaTeX:
+
+```latex
+% arara: pdflatex: {shell: on}
+\documentclass{article}
+
+\usepackage{texosquery}
+\usepackage{etoolbox}
+
+\begin{document}
+
+\TeXOSQuery{\result}{-b}
+IEFT BCP 47 language tag: \result.
+
+\TeXOSQueryLocale{\result}
+POSIX locale: \result.
+
+\TeXOSQueryCwd{\result}
+cwd: \texttt{\result}.
+
+\TeXOSQueryNow{\result}
+now: \result.
+
+\TeXOSQueryFileDate{\result}{\jobname.tex}
+file date: \result.
+
+\TeXOSQueryFileSize{\result}{\jobname.tex}
+file size: \result bytes.
+
+\TeXOSQueryFilterFileList{\result}{,}{.+\string\.(png|jpg)}{.}
+All jpg and png files in current directory:
+
+\renewcommand{\do}[1]{\texttt{#1}.\par}
+\expandafter\docsvlist\expandafter{\result}
+
+\end{document}
+```
+
+For a full list of available commands, see the documentation
+(`texosquery.pdf`).
+
+## Troubleshooting
+
+If something goes wrong with the call to `texosquery`, the control
+sequence will be set to empty. If this happens, here are the steps
+to diagnose the problem:
+
+ 1. Check the log file for any lines starting with `TeXOSQuery:`
+ For example: `TeXOSQuery: texosquery-jre8 --pdfnow`
+ If found, this means that the dry run mode was on and the shell
+ escape wasn't used. Ensure that the shell escape is
+ enabled when you build the document.
+ 2. If the log file contains `(|texosquery` _options_`)` then the
+ shell escape was used but `texosquery` returned an empty value.
+ In this case, copy the part between `(|` and `)` and paste it
+ into a command prompt or terminal but insert `--debug` at the
+ start of the options list. For example, if the log file
+ contains `(|texosquery-jre8 --cwd)` then run
+```sh
+texosquery-jre8 --debug --cwd
+```
+
+This should now display an error message explaining the
+problem. (For example, read access forbidden or file not found
+or a security exception.)
+
+## Security
+
+As from version 1.2, all variants of `texosquery` (JRE5, 7 and 8)
+obey TeX Live's `openin_any` setting. Any of the actions that involve
+reading file information won't work if read access is forbidden by
+`openin_any` or by the operating system. MiKTeX doesn't use the
+`openin_any` setting, so if not set `texosquery` will assume `a`
+(any).
+
+In addition to obeying `openin_any`, the file listing actions (such
+as `--list`) for the JRE7 and 8 variants also prohibit listing the
+contents outside of the current working directory's path. This means
+that you can't, for example, list the contents of `..` (the current
+working directory's parent) nor can you try walking the entire file
+system. The `--walk` action additionally won't descend hidden
+directories. This extra restriction is designed to prevent malicious
+code from trying to use `texosquery` to look around your filing
+system.
+
+The Java 5 version `texosquery-jre5` is the least secure and doesn't
+have this additional restriction on the file listing actions. However,
+it still obeys `openin_any`. The `--walk` action is not available
+with `texosquery-jre5`.
+
+Examples:
+```bash
+texosquery-jre8 --debug --walk ',' '.*' /
+```
+This action is forbidden:
+```
+texosquery-jre8: Can't walk directory: /
+```
+Changing `/` to `.` is allowed, but not for `texosquery-jre5`:
+```bash
+texosquery-jre5 --debug --walk ',' '.*' .
+```
+returns:
+```
+texosquery-jre5: walk requires at least JRE 7 version
+```
+With `openin_any=a`, the `--userhome` action is usually allowed
+(depends on the operating system and Java's security manager).
+```bash
+texosquery-jre8 --debug --userhome
+```
+returns
+```
+\fslh home\fslh nlct
+```
+(which `\TeXOSQuery` converts to `/home/nlct`.)
+
+However, unless this also happens to be the current working
+directory, it's not possible to obtain a file listing:
+```bash
+texosquery-jre8 --debug --list ',' /home/nlct
+```
+returns
+```
+texosquery-jre8: Unable to list contents of: /home/nlct
+texosquery-jre8: Listing outside cwd path not permitted: /home/nlct
+```
+
+## Source code
+
+The Java source is in the `.java` files and the manifest for each
+`.jar` file is `Manifest*.txt` (`Manifest-jre8.txt` for `texosquery-jre8` etc).
+Assuming the following directory structure:
+```
+java/TeXOSQuery.java
+java/TeXOSQueryJRE5.java
+java/TeXOSQueryJRE7.java
+java/TeXOSQueryJRE8.java
+java/QueryAction.java
+java/QueryActionType.java
+java/FileSortType.java
+java/FileSortComparator.java
+java/FileListType.java
+java/FileWalkVisitor.java
+java/Manifest-jre5.txt
+java/Manifest-jre7.txt
+java/Manifest-jre8.txt
+classes/com/dickimawbooks/texosquery/
+```
+Then to create `texosquery-jre8.jar`, do (for JDK version 1.8):
+```bash
+cd java
+javac -d ../classes TeXOSQuery.java QueryAction.java QueryActionType.java TeXOSQueryJRE8.java FileSortType.java FileSortComparator.java FileListType.java FileWalkVisitor.java
+cd ../classes
+jar cmf ../java/Manifest-jre8.txt ../texosquery-jre8.jar com/dickimawbooks/texosquery/*.class
+```
+
+Similarly for the other `.jar` files, replacing `8` with `7` or `5`.
+(The Java 7 version should really be `texosquery-jre7.jar`, but is
+simply named `texosquery.jar` in the distribution for backward compatibility reasons. If you compile the code, the `.jar` name is your choice.)
+
+---
+
+Source on GitHub: https://github.com/nlct/texosquery
+
+Author Home Page: http://www.dickimaw-books.com/
+
+License: LPPL 1.3+
diff --git a/support/texosquery/java/FileListType.java b/support/texosquery/java/FileListType.java
new file mode 100644
index 0000000000..46d4732fc4
--- /dev/null
+++ b/support/texosquery/java/FileListType.java
@@ -0,0 +1,24 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+/**
+ * Indicates the type of file to be included in listings.
+ * @since 1.2
+ */
+public enum FileListType
+{
+ FILE_LIST_ANY, FILE_LIST_DIRECTORIES_ONLY,
+ FILE_LIST_REGULAR_FILES_ONLY
+}
+
diff --git a/support/texosquery/java/FilePathSortComparator.java b/support/texosquery/java/FilePathSortComparator.java
new file mode 100644
index 0000000000..436a23867b
--- /dev/null
+++ b/support/texosquery/java/FilePathSortComparator.java
@@ -0,0 +1,186 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+import java.util.Comparator;
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.nio.file.attribute.FileTime;
+
+/**
+ * Used to compare two paths according to the given sort type.
+ * This uses the java.nio.file library, which was introduced in Java 7,
+ * so this isn't available for the JRE5 version.
+ * @since 1.2
+ */
+public class FilePathSortComparator implements Comparator<Path>
+{
+ /**
+ * Creates a new comparator for ordering path listings.
+ * @param sortType the way in which the files should be ordered
+ */
+ public FilePathSortComparator(FileSortType sortType)
+ {
+ this.sortType = sortType;
+ }
+
+ /**
+ * Compares two paths according to this object's sort type.
+ * @param path1 the first path
+ * @param path2 the second path
+ * @return -1 if path1 is considered less than path2, 0 if both are the same
+ * or +1 if path2 is greater than path1, according to the sort
+ * criteria
+ */
+ @Override
+ public int compare(Path path1, Path path2)
+ {
+ int idx=-1;
+ int result=0;
+ long date1=0L;
+ long date2=0L;
+ long size1=0L;
+ long size2=0L;
+ String ext1="";
+ String ext2="";
+ FileTime time1=null;
+ FileTime time2=null;
+
+ String name1 = path1.toString();
+ String name2 = path2.toString();
+
+ String basename1 = path1.getName(path1.getNameCount()-1).toString();
+ String basename2 = path2.getName(path2.getNameCount()-1).toString();
+
+ switch (sortType)
+ {
+ case FILE_SORT_NAME_ASCENDING:
+ return name1.compareTo(name2);
+ case FILE_SORT_NAME_DESCENDING:
+ return name2.compareTo(name1);
+ case FILE_SORT_NAME_NOCASE_ASCENDING:
+ return name1.compareToIgnoreCase(name2);
+ case FILE_SORT_NAME_NOCASE_DESCENDING:
+ return name2.compareToIgnoreCase(name1);
+ case FILE_SORT_EXT_ASCENDING:
+
+ idx = basename1.lastIndexOf(".");
+
+ ext1 = idx > -1 ? basename1.substring(idx+1) : "";
+
+ idx = basename2.lastIndexOf(".");
+
+ ext2 = idx > -1 ? basename2.substring(idx+1) : "";
+
+ result = ext1.compareTo(ext2);
+
+ // If the extensions are the same, compare paths instead
+
+ return result == 0 ? name1.compareTo(name2) : result;
+
+ case FILE_SORT_EXT_DESCENDING:
+
+ idx = basename1.lastIndexOf(".");
+
+ ext1 = idx > -1 ? basename1.substring(idx+1) : "";
+
+ idx = basename2.lastIndexOf(".");
+
+ ext2 = idx > -1 ? basename2.substring(idx+1) : "";
+
+ result = ext2.compareTo(ext1);
+
+ return result == 0 ? name2.compareTo(name1) : result;
+
+ case FILE_SORT_DATE_ASCENDING:
+
+ try
+ {
+ time1 = Files.getLastModifiedTime(path1);
+ }
+ catch (Exception e)
+ {
+ // IO error or security manager has prohibited access
+ return 1;
+ }
+
+ try
+ {
+ time2 = Files.getLastModifiedTime(path2);
+ }
+ catch (Exception e)
+ {
+ // IO error or security manager has prohibited access
+ return -1;
+ }
+
+ return time1.compareTo(time2);
+
+ case FILE_SORT_DATE_DESCENDING:
+
+ try
+ {
+ time1 = Files.getLastModifiedTime(path1);
+ }
+ catch (Exception e)
+ {
+ // IO error or security manager has prohibited access
+ return -1;
+ }
+
+ try
+ {
+ time2 = Files.getLastModifiedTime(path2);
+ }
+ catch (Exception e)
+ {
+ // IO error or security manager has prohibited access
+ return 1;
+ }
+
+ return time2.compareTo(time1);
+
+ case FILE_SORT_SIZE_ASCENDING:
+
+ try
+ {
+ size1 = Files.size(path1);
+ size2 = Files.size(path2);
+ }
+ catch (Exception e)
+ {// file missing or no read access or for some other
+ // reason the file size can't be obtained.
+ }
+
+ return size1 == size2 ? 0 : (size1 < size2 ? -1 : 0);
+
+ case FILE_SORT_SIZE_DESCENDING:
+
+ try
+ {
+ size1 = Files.size(path1);
+ size2 = Files.size(path2);
+ }
+ catch (Exception e)
+ {// file missing or no read access or for some other
+ // reason the file size can't be obtained.
+ }
+
+ return size1 == size2 ? 0 : (size1 > size2 ? -1 : 0);
+ }
+
+ return 0;
+ }
+
+ private FileSortType sortType;
+}
diff --git a/support/texosquery/java/FileSortComparator.java b/support/texosquery/java/FileSortComparator.java
new file mode 100644
index 0000000000..d382e07f30
--- /dev/null
+++ b/support/texosquery/java/FileSortComparator.java
@@ -0,0 +1,170 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+import java.util.Comparator;
+import java.io.File;
+
+/**
+ * Used to compare two file names according to the given sort type.
+ * @since 1.2
+ */
+public class FileSortComparator implements Comparator<String>
+{
+ /**
+ * Creates a new comparator for ordering file listings.
+ * @param baseDir the directory containing the listed files
+ * @param sortType the way in which the files should be ordered
+ */
+ public FileSortComparator(File baseDir, FileSortType sortType)
+ {
+ this.baseDir = baseDir;
+ this.sortType = sortType;
+ }
+
+ /**
+ * Compares two files according to this object's sort type.
+ * @param name1 the name of the first file
+ * @param name2 the name of the second file
+ * @return -1 if name1 is considered less than name2, 0 if both are the same
+ * or +1 if name2 is greater than name1, according to the sort
+ * criteria
+ */
+ @Override
+ public int compare(String name1, String name2)
+ {
+ int idx=-1;
+ int result=0;
+ long date1=0L;
+ long date2=0L;
+ long size1=0L;
+ long size2=0L;
+ String ext1="";
+ String ext2="";
+ File file1;
+ File file2;
+
+ switch (sortType)
+ {
+ case FILE_SORT_NAME_ASCENDING:
+ return name1.compareTo(name2);
+ case FILE_SORT_NAME_DESCENDING:
+ return name2.compareTo(name1);
+ case FILE_SORT_NAME_NOCASE_ASCENDING:
+ return name1.compareToIgnoreCase(name2);
+ case FILE_SORT_NAME_NOCASE_DESCENDING:
+ return name2.compareToIgnoreCase(name1);
+ case FILE_SORT_EXT_ASCENDING:
+
+ idx = name1.lastIndexOf(".");
+
+ ext1 = idx > -1 ? name1.substring(idx+1) : "";
+
+ idx = name2.lastIndexOf(".");
+
+ ext2 = idx > -1 ? name2.substring(idx+1) : "";
+
+ result = ext1.compareTo(ext2);
+
+ return result == 0 ? name1.compareTo(name2) : result;
+
+ case FILE_SORT_EXT_DESCENDING:
+
+ idx = name1.lastIndexOf(".");
+
+ ext1 = idx > -1 ? name1.substring(idx+1) : "";
+
+ idx = name2.lastIndexOf(".");
+
+ ext2 = idx > -1 ? name2.substring(idx+1) : "";
+
+ result = ext2.compareTo(ext1);
+
+ return result == 0 ? name2.compareTo(name1) : result;
+
+ case FILE_SORT_DATE_ASCENDING:
+
+ file1 = new File(baseDir, name1);
+ file2 = new File(baseDir, name2);
+
+ try
+ {
+ date1 = file1.lastModified();
+ date2 = file2.lastModified();
+ }
+ catch (Exception e)
+ {// file missing or no read access or for some other
+ // reason the last modified date can't be obtained.
+ }
+
+ return date1 == date2 ? 0 : (date1 < date2 ? -1 : 0);
+
+ case FILE_SORT_DATE_DESCENDING:
+
+ file1 = new File(baseDir, name1);
+ file2 = new File(baseDir, name2);
+
+ try
+ {
+ date1 = file1.lastModified();
+ date2 = file2.lastModified();
+ }
+ catch (Exception e)
+ {// file missing or no read access or for some other
+ // reason the last modified date can't be obtained.
+ }
+
+ return date1 == date2 ? 0 : (date1 > date2 ? -1 : 0);
+
+ case FILE_SORT_SIZE_ASCENDING:
+
+ file1 = new File(baseDir, name1);
+ file2 = new File(baseDir, name2);
+
+ try
+ {
+ size1 = file1.length();
+ size2 = file2.length();
+ }
+ catch (Exception e)
+ {// file missing or no read access or for some other
+ // reason the file size can't be obtained.
+ }
+
+ return size1 == size2 ? 0 : (size1 < size2 ? -1 : 0);
+
+ case FILE_SORT_SIZE_DESCENDING:
+
+ file1 = new File(baseDir, name1);
+ file2 = new File(baseDir, name2);
+
+ try
+ {
+ size1 = file1.length();
+ size2 = file2.length();
+ }
+ catch (Exception e)
+ {// file missing or no read access or for some other
+ // reason the file size can't be obtained.
+ }
+
+ return size1 == size2 ? 0 : (size1 > size2 ? -1 : 0);
+ }
+
+ return 0;
+ }
+
+
+ private File baseDir;
+ private FileSortType sortType;
+}
diff --git a/support/texosquery/java/FileSortType.java b/support/texosquery/java/FileSortType.java
new file mode 100644
index 0000000000..4b3e303ff2
--- /dev/null
+++ b/support/texosquery/java/FileSortType.java
@@ -0,0 +1,131 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+/**
+ * Indicates the ordering of files in listings.
+ * @since 1.2
+ */
+public enum FileSortType
+{
+ FILE_SORT_DEFAULT ("default"),
+ FILE_SORT_DATE_ASCENDING ("date-ascending", "date", "date-asc"),
+ FILE_SORT_DATE_DESCENDING ("date-descending", "date-des"),
+ FILE_SORT_SIZE_ASCENDING ("size-ascending", "size", "size-asc"),
+ FILE_SORT_SIZE_DESCENDING ("size-descending", "size-des"),
+ FILE_SORT_NAME_ASCENDING ("name-ascending", "name", "name-asc"),
+ FILE_SORT_NAME_DESCENDING ("name-descending", "name-des"),
+ FILE_SORT_NAME_NOCASE_ASCENDING ("iname-ascending", "iname", "iname-asc"),
+ FILE_SORT_NAME_NOCASE_DESCENDING ("iname-descending", "iname-des"),
+ FILE_SORT_EXT_ASCENDING ("ext-ascending", "ext", "ext-asc"),
+ FILE_SORT_EXT_DESCENDING ("ext-descending", "ext-des");
+
+ private final String name, altName1, altName2;
+
+ FileSortType(String name)
+ {
+ this(name, null, null);
+ }
+
+ FileSortType(String name, String altName)
+ {
+ this(name, altName, null);
+ }
+
+ FileSortType(String name, String altName1, String altName2)
+ {
+ this.name = name;
+ this.altName1 = altName1;
+ this.altName2 = altName2;
+ }
+
+ /**
+ * Returns available option names.
+ */
+ private String options()
+ {
+ if (altName1 == null && altName2 == null)
+ {
+ return name;
+ }
+
+ if (altName2 == null)
+ {
+ return String.format("%s (or %s)", name, altName1);
+ }
+
+ return String.format("%s (or %s or %s)", name, altName1, altName2);
+ }
+
+ /**
+ * Checks if the given type matches this option.
+ * @return true if type matches this option
+ */
+ private boolean isOption(String type)
+ {
+ return type.equals(name) || type.equals(altName1)
+ || type.equals(altName2);
+ }
+
+ /**
+ * Returns a list of available sort options used in the command
+ * line invocation.
+ * @return list of options for syntax information.
+ */
+ public static String getFileSortOptions()
+ {
+ StringBuilder builder = new StringBuilder();
+
+ for (FileSortType t : FileSortType.values())
+ {
+ if (builder.length() > 0)
+ {
+ builder.append(", ");
+ }
+
+ builder.append(t.options());
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Gets the sort type from the given string.
+ * @param type the sort type as provided by the command line
+ * invocation
+ * @return a FileSortType object that indicates the sort type
+ */
+ public static FileSortType getFileSortType(String type)
+ {
+ if (type == null)
+ {
+ return FileSortType.FILE_SORT_DEFAULT;
+ }
+
+ for (FileSortType t : FileSortType.values())
+ {
+ if (t.isOption(type))
+ {
+ return t;
+ }
+ }
+
+ throw new IllegalArgumentException( "Invalid sort type: "+type);
+ }
+
+ public String toString()
+ {
+ return name;
+ }
+}
+
diff --git a/support/texosquery/java/FileWalkVisitor.java b/support/texosquery/java/FileWalkVisitor.java
new file mode 100644
index 0000000000..89e4fe7381
--- /dev/null
+++ b/support/texosquery/java/FileWalkVisitor.java
@@ -0,0 +1,221 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.nio.file.FileVisitResult;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Vector;
+import java.util.Comparator;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * This class is used during a file walk to determine if a directory
+ * may be descended and to add allowed files to the list.
+ * This relies on the java.nio.file library, which was introduced to
+ * Java 7, so this isn't available for the JRE5 version.
+ * @since 1.2
+ */
+public class FileWalkVisitor extends SimpleFileVisitor<Path>
+{
+ /**
+ * Creates a new visitor for a file walk. This will only allow
+ * regular, non-hidden, readable files, where the name matches
+ * the supplied regular expression. The found files are inserted
+ * into the results list according to the comparator.
+ */
+ public FileWalkVisitor(TeXOSQuery invoker, String regex,
+ Comparator<Path> comparator)
+ {
+ this.invoker = invoker;
+ this.comparator = comparator;
+ pattern = Pattern.compile(regex);
+ result = new Vector<Path>();
+ }
+
+ /**
+ * Returns the list built during the file walk.
+ * @return file list
+ */
+ public Vector<Path> getList()
+ {
+ return result;
+ }
+
+ /**
+ * Invoked for a directory before entries in the directory are
+ * visited.
+ *
+ * Hidden directories are automatically skipped, even if
+ * openin_any is <tt>a</tt>. Symbolic links and directories without
+ * read access are also skipped.
+ *
+ * @param dir the directory about to be visited
+ * @param attrs the directory's basic attributes
+ * @return the visit result
+ */
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
+ throws IOException
+ {
+ if (Files.isHidden(dir) || !Files.isReadable(dir)
+ || Files.isSymbolicLink(dir))
+ {
+ invoker.info(String.format(
+ "Walk skipping directory: %s", dir.toString()));
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+
+ // We don't need to check openin_any here as the starting
+ // directory of the walk should already have been checked to
+ // ensure it's on the CWD path.
+
+ return FileVisitResult.CONTINUE;
+ }
+
+
+ /**
+ * Invoked for a file in a directory.
+ *
+ * Hidden files are automatically skipped, regardless of
+ * openin_any. Files that don't have read access are also skipped
+ * and so are symbolic files.
+ *
+ * @param file a reference to the file
+ * @param attrs the file's basic attributes
+ * @return the visit result
+ */
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException
+ {
+ if (Files.isHidden(file) || !Files.isReadable(file)
+ || attrs.isSymbolicLink())
+ {
+ invoker.info(String.format(
+ "Walk skipping file: %s", file.toString()));
+ return FileVisitResult.CONTINUE;
+ }
+
+ // Does the file name match the supplied pattern?
+
+ String name = file.getName(file.getNameCount()-1).toString();
+
+ Matcher m = pattern.matcher(name);
+
+ if (m.matches())
+ {
+ addPath(file);
+ }
+
+ return FileVisitResult.CONTINUE;
+ }
+
+ /**
+ * Inserts path into results list according to the comparator.
+ * @param path the path to be added to the list
+ */
+ private void addPath(Path path)
+ {
+ int n = result.size();
+
+ if (n == 0)
+ {
+ result.add(path);
+ return;
+ }
+
+ for (int i = 0; i < n; i++)
+ {
+ Path thisPath = result.get(i);
+
+ if (comparator.compare(thisPath, path) > 0)
+ {
+ result.add(i, path);
+ return;
+ }
+ }
+
+ result.add(path);
+ }
+
+ /**
+ *Walks the path starting from the given directory, which must be the
+ *current working directory or a descendent.
+ */
+ public static String walk(TeXOSQuery invoker,
+ String separator,
+ String regex,
+ File directory,
+ FileSortType sortType)
+ throws IOException
+ {
+ if (!directory.exists())
+ {
+ throw new FileNotFoundException(
+ String.format("No such file: %s", directory.toString()));
+ }
+
+ if (!directory.isDirectory())
+ {
+ throw new IOException(
+ String.format("Not a directory: %s", directory.toString()));
+ }
+
+ directory = directory.getCanonicalFile();
+
+ File cwd = new File(invoker.getSystemProperty("user.dir", "."));
+
+ if (!cwd.equals(directory) && !invoker.isFileInTree(directory, cwd))
+ {
+ throw new IOException(
+ String.format("Walk must start in current working directory path: %s",
+ cwd.toString()));
+ }
+
+ Path start = directory.toPath();
+
+ FileWalkVisitor visitor = new FileWalkVisitor(invoker, regex,
+ new FilePathSortComparator(sortType));
+
+ Files.walkFileTree(start, visitor);
+
+ Vector<Path> result = visitor.getList();
+
+ StringBuilder builder = new StringBuilder();
+
+ for (Path path : result)
+ {
+ if (builder.length() > 0)
+ {
+ builder.append(separator);
+ }
+
+ builder.append(
+ invoker.escapeFileName(start.relativize(path).toString()));
+ }
+
+ return builder.toString();
+ }
+
+ private TeXOSQuery invoker;
+ private Pattern pattern;
+ private Vector<Path> result;
+ private Comparator<Path> comparator;
+}
diff --git a/support/texosquery/java/Manifest-jre5.txt b/support/texosquery/java/Manifest-jre5.txt
new file mode 100644
index 0000000000..f34ae37301
--- /dev/null
+++ b/support/texosquery/java/Manifest-jre5.txt
@@ -0,0 +1,2 @@
+Main-Class: com/dickimawbooks/texosquery/TeXOSQueryJRE5
+Class-Path: .
diff --git a/support/texosquery/java/Manifest-jre7.txt b/support/texosquery/java/Manifest-jre7.txt
new file mode 100644
index 0000000000..b5669d1e00
--- /dev/null
+++ b/support/texosquery/java/Manifest-jre7.txt
@@ -0,0 +1,2 @@
+Main-Class: com/dickimawbooks/texosquery/TeXOSQueryJRE7
+Class-Path: .
diff --git a/support/texosquery/java/Manifest-jre8.txt b/support/texosquery/java/Manifest-jre8.txt
new file mode 100644
index 0000000000..0e460823ef
--- /dev/null
+++ b/support/texosquery/java/Manifest-jre8.txt
@@ -0,0 +1,2 @@
+Main-Class: com/dickimawbooks/texosquery/TeXOSQueryJRE8
+Class-Path: .
diff --git a/support/texosquery/java/QueryAction.java b/support/texosquery/java/QueryAction.java
new file mode 100644
index 0000000000..b50e65cc3a
--- /dev/null
+++ b/support/texosquery/java/QueryAction.java
@@ -0,0 +1,448 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+import java.io.Serializable;
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+
+/**
+ * Class representing an action to be performed by the application.
+ * @since 1.2
+ */
+public abstract class QueryAction implements Serializable
+{
+ public QueryAction()
+ {
+ }
+
+ public QueryAction(String longForm, QueryActionType type,
+ String description)
+ {
+ this(longForm, null, 0, 0, "", type, description);
+ }
+
+ public QueryAction(String longForm, QueryActionType type,
+ String description, int minCompat)
+ {
+ this(longForm, null, 0, 0, "", type, description, minCompat);
+ }
+
+ public QueryAction(String longForm, String shortForm,
+ QueryActionType type, String description)
+ {
+ this(longForm, shortForm, 0, 0, "", type, description);
+ }
+
+ public QueryAction(String longForm, String shortForm,
+ QueryActionType type, String description, int minCompat)
+ {
+ this(longForm, shortForm, 0, 0, "", type, description, minCompat);
+ }
+
+ public QueryAction(String longForm, String shortForm, String syntax,
+ QueryActionType type, String description)
+ {
+ this(longForm, shortForm, 0, 0, syntax, type, description);
+ }
+
+ public QueryAction(String longForm, String shortForm,
+ int numOptional, int numRequired, String syntax,
+ QueryActionType type, String description)
+ {
+ this(longForm, shortForm, numOptional, numRequired,
+ syntax, type, description, 0);
+ }
+
+ public QueryAction(String longForm, String shortForm,
+ int numOptional, int numRequired, String syntax,
+ QueryActionType type, String description, int minCompat)
+ {
+ if (longForm != null)
+ {
+ longName = "--"+longForm;
+ }
+
+ if (shortForm != null)
+ {
+ shortName = "-"+shortForm;
+ }
+
+ optional = numOptional;
+ required = numRequired;
+
+ this.syntax = syntax;
+ this.type = type;
+ this.description = description;
+ this.minCompatibility = minCompat;
+ }
+
+ public boolean isAction(String name)
+ {
+ return name.equals(shortName) || name.equals(longName)
+ || (longName != null && name.startsWith(longName+"="));
+ }
+
+ public int parseArgs(String[] args, int index)
+ throws IllegalArgumentException
+ {
+ if (required > 0)
+ {
+ requiredArgs = new String[required];
+ }
+
+ if (args[index].startsWith(longName+"="))
+ {
+ int idx = args[index].indexOf("=")+1;
+ invokedName = longName;
+
+ if (idx == args[index].length())
+ {
+ if (required == 1)
+ {
+ requiredArgs[0] = "";
+ }
+ else
+ {
+ throw new IllegalArgumentException(String.format(
+ "Invalid syntax for action '%s'.%nExpected: %s",
+ invokedName, getUsage(invokedName)));
+ }
+
+ return index+1;
+ }
+
+ String[] sp;
+
+ if (required+optional > 1)
+ {
+ sp = args[index].substring(idx).split(" ");
+ }
+ else
+ {
+ sp = new String[]{args[index].substring(idx)};
+ }
+
+ idx = 0;
+
+ for (int i = 0; i < required; i++)
+ {
+ if (i >= sp.length)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Invalid syntax for action '%s'.%nExpected: %s",
+ invokedName, getUsage(invokedName)));
+ }
+
+ requiredArgs[i] = sp[idx++];
+ }
+
+ optionalProvided = 0;
+
+ if (optional > 0)
+ {
+ optionalArgs = new String[optional];
+ }
+
+ for (int i = 0; i < optional; i++)
+ {
+ if (idx >= sp.length)
+ {
+ // no optional arguments, skip
+ break;
+ }
+
+ optionalArgs[optionalProvided++] = sp[idx++];
+ }
+
+ return index+1;
+ }
+ else
+ {
+ invokedName = args[index++];
+
+ for (int i = 0; i < required; i++)
+ {
+ if (index >= args.length)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Invalid syntax for action '%s'.%nExpected: %s",
+ invokedName, getUsage(invokedName)));
+ }
+
+ requiredArgs[i] = args[index++];
+ }
+
+ optionalProvided = 0;
+
+ if (optional > 0)
+ {
+ optionalArgs = new String[optional];
+ }
+
+ for (int i = 0; i < optional; i++)
+ {
+ if (index >= args.length || args[index].startsWith("-"))
+ {
+ // no optional arguments, skip
+ break;
+ }
+
+ optionalArgs[optionalProvided++] = args[index++];
+ }
+ }
+
+ return index;
+ }
+
+ public int providedOptionCount()
+ {
+ return optionalProvided;
+ }
+
+ public String getOptionalArgument(int index)
+ {
+ return index < optionalArgs.length ? optionalArgs[index] : null;
+ }
+
+ public String getRequiredArgument(int index)
+ {
+ return requiredArgs[index];
+ }
+
+ public String getInvocation()
+ {
+ StringBuilder builder = new StringBuilder(getInvokedName());
+
+ if (requiredArgs != null)
+ {
+ for (String arg : requiredArgs)
+ {
+ if (arg.contains(" "))
+ {
+ builder.append(String.format(" '%s'",
+ arg.replaceAll("'", "\\\\'")));
+ }
+ else
+ {
+ builder.append(String.format(" %s", arg));
+ }
+ }
+ }
+
+ if (optionalArgs != null)
+ {
+ for (String arg : optionalArgs)
+ {
+ if (arg == null) continue;
+
+ if (arg.contains(" "))
+ {
+ builder.append(String.format(" '%s'",
+ arg.replaceAll("'", "\\\\'")));
+ }
+ else
+ {
+ builder.append(String.format(" %s", arg));
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ public String getInvokedName()
+ {
+ return invokedName;
+ }
+
+ public QueryActionType getType()
+ {
+ return type;
+ }
+
+ public String getUsage(String name)
+ {
+ return String.format("%s%s",
+ name, syntax == null || "".equals(syntax) ? "" : " "+syntax);
+ }
+
+ /**
+ * Formats the description to fit maximum of 80 character line
+ * width.
+ */
+ private String formatDescription()
+ {
+ int n = description.length();
+ int max = MAX_CHARS_PER_LINE-8;
+
+ if (n < max)
+ {
+ return description;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ StringBuilder line = new StringBuilder();
+ StringBuilder word = new StringBuilder();
+
+ for (int i = 0, offset=1; i < n; i += offset)
+ {
+ int codepoint = description.codePointAt(i);
+ offset = Character.charCount(codepoint);
+
+ if (codepoint == ' ')
+ {
+ if (line.length()+word.length()+1 >= max)
+ {
+ builder.append(line);
+ builder.append(String.format("%n"));
+ line = new StringBuilder();
+ line.append('\t');
+ }
+
+ line.append(word);
+ line.append(' ');
+ word = new StringBuilder();
+ }
+ else
+ {
+ word.appendCodePoint(codepoint);
+ }
+ }
+
+ if (line.length()+word.length()+1 >= max)
+ {
+ builder.append(line);
+ builder.append(String.format("%n"));
+ line = new StringBuilder();
+ line.append('\t');
+ }
+
+ line.append(word);
+ builder.append(line);
+
+ return builder.toString();
+ }
+
+ public String help()
+ {
+ String usage;
+
+ if (shortName == null)
+ {
+ usage = getUsage(longName);
+ }
+ else
+ {
+ usage = String.format("%s or %s", getUsage(shortName),
+ getUsage(longName));
+ }
+
+ int n = usage.length();
+
+ return String.format("%s%n\t%s.%n", usage, formatDescription());
+ }
+
+ public String doAction(int compatible) throws IllegalArgumentException
+ {
+ if (compatible < minCompatibility)
+ {
+ throw new IllegalArgumentException(String.format(
+ "'%s' option not available in compatibility mode %d",
+ invokedName, compatible));
+ }
+
+ return action();
+ }
+
+ protected abstract String action();
+
+ /**
+ * Make a copy of this object.
+ */
+ public final QueryAction copy()
+ {
+ try
+ {
+ ByteArrayOutputStream byteStr = new ByteArrayOutputStream();
+
+ new ObjectOutputStream(byteStr).writeObject(this);
+
+ QueryAction action = (QueryAction) new ObjectInputStream(
+ new ByteArrayInputStream(byteStr.toByteArray())).readObject();
+ action.same(this);
+
+ return action;
+ }
+ catch (Exception e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ private void same(QueryAction action)
+ {
+ longName = action.longName;
+ shortName = action.shortName;
+ invokedName = action.invokedName;
+ syntax = action.syntax;
+ optional = action.optional;
+ optionalProvided = action.optionalProvided;
+ required = action.required;
+ type = action.type;
+ description = action.description;
+ minCompatibility = action.minCompatibility;
+
+ if (action.optionalArgs != null)
+ {
+ optionalArgs = new String[action.optionalArgs.length];
+
+ for (int i = 0; i < action.optionalArgs.length; i++)
+ {
+ optionalArgs[i] = action.optionalArgs[i];
+ }
+ }
+
+ if (action.requiredArgs != null)
+ {
+ requiredArgs = new String[action.requiredArgs.length];
+
+ for (int i = 0; i < action.requiredArgs.length; i++)
+ {
+ requiredArgs[i] = action.requiredArgs[i];
+ }
+ }
+ }
+
+ public String getLongName()
+ {
+ return longName;
+ }
+
+ private String longName=null;
+ private String shortName=null;
+ private String invokedName=null;
+ private String syntax="";
+ private int optional=0;
+ private int optionalProvided=0;
+ private int required=0;
+ private String[] requiredArgs=null;
+ private String[] optionalArgs=null;
+ private QueryActionType type;
+ private String description;
+ private int minCompatibility=0;
+
+ private static final int MAX_CHARS_PER_LINE=80;
+}
diff --git a/support/texosquery/java/QueryActionType.java b/support/texosquery/java/QueryActionType.java
new file mode 100644
index 0000000000..0eb2382766
--- /dev/null
+++ b/support/texosquery/java/QueryActionType.java
@@ -0,0 +1,19 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+public enum QueryActionType
+{
+ FILE_ACTION, LOCALE_ACTION, GENERAL_ACTION
+}
+
diff --git a/support/texosquery/java/TeXOSQuery.java b/support/texosquery/java/TeXOSQuery.java
new file mode 100644
index 0000000000..9f97e85886
--- /dev/null
+++ b/support/texosquery/java/TeXOSQuery.java
@@ -0,0 +1,4805 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+import java.io.*;
+import java.util.Locale;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.Vector;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.text.DecimalFormatSymbols;
+import java.text.Format;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.text.DateFormatSymbols;
+import java.text.NumberFormat;
+import java.text.DecimalFormat;
+import java.nio.charset.*;
+
+/**
+ * Application functions. These methods need to be Java version 1.5
+ * compatible. The 1.7 methods need to be in the TeXOSQueryJRE7 class
+ * (which provides the main part of texosquery.jar) and the 1.8 methods in
+ * TeXOSQueryJRE8 (which provides the main part of texosquery-jre8.jar).
+ *
+ * The texosquery-jre5.jar version should not be considered secure
+ * and is only provided for antiquated systems.
+ * Java 5 and 6 are both deprecated and are now considered security
+ * risks.
+ *
+ * Since this application is designed to be run from TeX, the output
+ * needs to be easy to parse using TeX commands. For this reason,
+ * most exceptions are caught and an empty string is returned. The
+ * TeX code can then check for an empty value to determine failure.
+ * There's a debug mode to print error messages to STDERR to
+ * investigate the reason for failure.
+ * @author Nicola Talbot
+ * @version 1.2.1
+ * @since 1.0
+ */
+public class TeXOSQuery implements Serializable
+{
+ /**
+ * Constructor.
+ * @param name The application name.
+ */
+ public TeXOSQuery(String name)
+ {
+ this.name = name;
+ }
+
+ /**
+ * Gets the application name.
+ * @return the application name
+ * @since 1.2
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Runs kpsewhich and returns the result. This is for single
+ * argument lookups through kpsewhich, such as a file location
+ * or variable value.
+ * @param arg The argument to pass to kpsewhich
+ * @return The result read from the first line of STDIN
+ * @since 1.2
+ */
+ protected String kpsewhich(String arg)
+ throws IOException,InterruptedException
+ {
+ // Create and start the process.
+ Process process =
+ new ProcessBuilder("kpsewhich", arg).start();
+
+ int exitCode = process.waitFor();
+
+ String line = null;
+
+ if (exitCode == 0)
+ {
+ // kpsewhich completed with exit code 0.
+ // Read STDIN to find the result.
+
+ InputStream stream = process.getInputStream();
+
+ if (stream == null)
+ {
+ throw new IOException(String.format(
+ "Unable to open input stream from process: kpsewhich '%s'",
+ arg));
+ }
+
+ BufferedReader reader = null;
+
+ try
+ {
+ reader = new BufferedReader(new InputStreamReader(stream));
+
+ // only read a single line, nothing further is required
+ // for a variable or file location query.
+ line = reader.readLine();
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.close();
+ }
+ }
+ }
+ else
+ {
+ // kpsewhich failed.
+
+ throw new IOException(String.format(
+ "\"kpsewhich '%s'\" failed with exit code: %d", arg, exitCode));
+ }
+
+ return line;
+ }
+
+ /**
+ * Print message if in debug mode. Message is printed to STDERR
+ * if the debug level is greater than or equal to the given level.
+ * Debugging messages are all written to STDERR rather than
+ * STDOUT so they show up in the transcript rather than being
+ * captured by the shell escape.
+ * @param message Debugging message.
+ * @param level Debugging level.
+ * @since 1.2
+ */
+ public void debug(String message, int level)
+ {
+ if (debugLevel >= level)
+ {
+ System.err.println(String.format("%s: %s", name, message));
+ }
+ }
+
+ /**
+ * Print message if in debug mode. Message is printed to STDERR
+ * if the debug level is 1 or more.
+ * @param message Debugging message.
+ * @since 1.2
+ */
+ public void debug(String message)
+ {
+ debug(message, DEBUG_ERROR_LEVEL);
+ }
+
+ /**
+ * Message if in debug mode. This is for information rather than
+ * errors. The message is printed to STDERR if the debug level
+ * is 3 or more.
+ * @param message Debugging message.
+ * @since 1.2
+ */
+ public void info(String message)
+ {
+ debug(message, DEBUG_INFO_LEVEL);
+ }
+
+ /**
+ * Print message and exception if in debug mode. Message is printed to
+ * STDERR if the debug level is greater than or equal to the given level.
+ * The exception may be null. If not null, the exception message
+ * is printed.
+ * @param message Debugging message.
+ * @param excpt Exception.
+ * @param msgLevel Debugging level for message.
+ * @param traceLevel Debugging level for stack trace.
+ * @since 1.2
+ */
+ public void debug(String message, Throwable excpt, int msgLevel,
+ int traceLevel)
+ {
+ debug(message, msgLevel);
+
+ if (excpt != null)
+ {
+ debug(excpt.getMessage(), msgLevel);
+
+ if (debugLevel >= traceLevel)
+ {
+ excpt.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Print message and exception if in debug mode. The message
+ * level is 1 and the trace level is 2.
+ * @param message Debugging message.
+ * @param excpt Exception.
+ * @since 1.2
+ */
+ public void debug(String message, Throwable excpt)
+ {
+ debug(message, excpt, DEBUG_ERROR_LEVEL, DEBUG_STACK_TRACE_LEVEL);
+ }
+
+ /**
+ * Checks if file is in or below the given directory. This might
+ * be easier with java.nio.file.Path etc but that requires Java
+ * 1.7, so use the old-fashioned method.
+ * @param file The file being checked
+ * @param dir The directory being searched
+ * @return true if found
+ * @since 1.2
+ */
+ protected boolean isFileInTree(File file, File dir)
+ throws IOException
+ {
+ if (file == null || dir == null) return false;
+
+ file = file.getCanonicalFile();
+ dir = dir.getCanonicalFile();
+
+ File parent = file.getParentFile();
+
+ while (parent != null)
+ {
+ if (parent.equals(dir))
+ {
+ return true;
+ }
+
+ parent = parent.getParentFile();
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if the given file is hidden.
+ * Java's File.isHidden() method seems to consider "." and ".."
+ * as hidden directories, so this method converts the file to a
+ * canonical path before testing.
+ * @param file The file to check
+ * @return True if the file is considered hidden.
+ * @since 1.2
+ */
+ public boolean isHidden(File file)
+ {
+ try
+ {
+ return file.getCanonicalFile().isHidden();
+ }
+ catch (IOException e)
+ {
+ // file can't be converted to a canonical path, so
+ // consider it hidden
+
+ debug(String.format(
+ "Unable to convert file to a canonical path: ",
+ file.toString()), e);
+ }
+
+ return true;
+ }
+
+ /**
+ * Fallback for openin_any if not found.
+ * @since 1.2.2
+ */
+ public char openinFallbackValue()
+ {
+ return OPENIN_A;
+ }
+
+ /**
+ * Queries if the given file may be read according to
+ * openin_any. Since the user may not require any of the file
+ * access functions, the openin variable is only set the first
+ * time this method is used to reduce unnecessary overhead.
+ * kpsewhich is used to lookup the value of openin_any, which
+ * may have one of the following values: a (any), r (restricted,
+ * no hidden files) or p (paranoid, as restricted and no parent
+ * directories and no absolute paths except under $TEXMFOUTPUT).
+ * Apparently with MikTeX, this variable isn't available, so we
+ * need a fallback for that case.
+ * @param file The file to be checked
+ * @return true if read-access allowed
+ * @since 1.2
+ */
+ public boolean isReadPermitted(File file)
+ {
+ // if file doesn't exist, it can't be read
+ if (file == null || !file.exists())
+ {
+ return false;
+ }
+
+ try
+ {
+ if (openin == OPENIN_UNSET)
+ {
+ //First time this method has been called. Use kpsewhich
+ //to determine the value.
+
+ try
+ {
+ String result = kpsewhich("-var-value=openin_any");
+
+ if ("a".equals(result))
+ {
+ openin=OPENIN_A;
+ }
+ else if ("r".equals(result))
+ {
+ openin=OPENIN_R;
+ }
+ else if ("p".equals(result))
+ {
+ openin=OPENIN_P;
+ }
+ else
+ {
+ // openin_any variable hasn't been set, use the
+ // fallback value.
+ openin = openinFallbackValue();
+ debug(String.format(
+ "Invalid openin_any value: %s%nUsing fallback value: %s",
+ result, openin));
+ }
+ }
+ catch (Exception e)
+ {
+ // kpsewhich failed, assume paranoid
+ debug("Can't determine openin value, assuming 'p'", e);
+ openin = OPENIN_P;
+ }
+
+ // Now find TEXMFOUTPUT if set (only need this with the
+ // paranoid setting)
+
+ if (openin == OPENIN_P)
+ {
+ String path = null;
+
+ try
+ {
+ path = System.getenv("TEXMFOUTPUT");
+ }
+ catch (SecurityException e)
+ {
+ debug("Can't query TEXMFOUTPUT", e);
+ }
+
+ if (path != null && !"".equals(path))
+ {
+ texmfoutput = new File(fromTeXPath(path));
+
+ if (!texmfoutput.exists())
+ {
+ debug(String.format(
+ "TEXMFOUTPUT doesn't exist, ignoring: %s",
+ texmfoutput.toString()));
+ texmfoutput = null;
+ }
+ else if (!texmfoutput.isDirectory())
+ {
+ debug(String.format(
+ "TEXMFOUTPUT isn't a directory, ignoring: %s",
+ texmfoutput.toString()));
+ texmfoutput = null;
+ }
+ else if (!texmfoutput.canRead())
+ {
+ debug(String.format(
+ "TEXMFOUTPUT doesn't have read permission, ignoring: %s",
+ texmfoutput.toString()));
+ texmfoutput = null;
+ }
+ }
+ }
+ }
+
+ // Now check if the given file can be read according to the
+ // openin setting.
+
+ switch (openin)
+ {
+ case OPENIN_A:
+ // any file can be read as long as the OS allows it
+ return file.canRead();
+ case OPENIN_P:
+ // paranoid check
+
+ if (isFileInTree(file, texmfoutput))
+ {
+ // file under TEXMFOUTPUT, so it's okay as long
+ // as it has read permission
+ return file.canRead();
+ }
+
+ // does the file have an absolute path?
+
+ if (file.isAbsolute())
+ {
+ debug(String.format(
+ "Read access forbidden by openin_any=%c (has absolute path outside TEXMFOUTPUT): %s",
+ openin, file));
+ return false;
+ }
+
+ // is the file outside the cwd?
+ File cwd = new File(getSystemProperty("user.dir", "."));
+
+ if (file.getParentFile() != null && !isFileInTree(file, cwd))
+ {
+ debug(String.format(
+ "Read access forbidden by openin_any=%c (outside cwd path): %s",
+ openin, file));
+ return false;
+ }
+
+ // no break, fall through to restricted check
+ case OPENIN_R:
+
+ if (isHidden(file))
+ {
+ // hidden file so not permitted
+ debug(String.format(
+ "Read access forbidden by openin_any=%c (hidden file): %s",
+ openin, file));
+ return false;
+ }
+
+ break;
+ default:
+ // this shouldn't happen, but just in case...
+ debug(String.format("Invalid openin value: %d", openin));
+ // don't allow, something's gone badly wrong
+ return false;
+ }
+
+ // return read access
+ return file.canRead();
+ }
+ catch (Exception e)
+ {
+ // Catch all exceptions
+ debug(String.format("Read permission check failed: %s", file), e);
+
+ // Can't permit read if something's gone wrong here.
+ return false;
+ }
+ }
+
+ /**
+ * Gets the given system property or the default value.
+ * Returns the default value if the property isn't set or can't be accessed.
+ * @param propName The property name
+ * @param defValue The default value
+ * @return The property value or the default if unavailable
+ * @since 1.2
+ */
+ public String getSystemProperty(String propName, String defValue)
+ {
+ try
+ {
+ return System.getProperty(propName, defValue);
+ }
+ catch (SecurityException e)
+ {
+ // The security manager doesn't permit access to this property.
+
+ debug(String.format("Unable to access property: %s", propName), e);
+ return defValue;
+ }
+ }
+
+ /**
+ * Escapes potentially problematic characters from a string that will be
+ * expanded when input by TeX's shell escape.
+ *
+ * Some of the methods in this class return TeX code. Those
+ * returned values shouldn't be escaped as it would interfere
+ * with the code, so just use this method on information
+ * directly obtained from Java. This will typically be either
+ * file names (in which case the characters within the string
+ * must all be "letter" or "other") or regular text for use in
+ * the document (such as dates or times, in which case the
+ * characters may be active to allow them to be correctly
+ * typeset, such as UTF-8 characters with inputenc.sty).
+ *
+ * The date-time and numeric patterns (such as "YYYY-MM-DD"
+ * or "#,##0.0") are dealt with elsewhere as they need different treatment.
+ *
+ * \\TeXOSQuery locally defines commands for characters
+ * used in file names (catcode 12). These are all in the form
+ * \\fxxx (such as \\fhsh for a literal hash). Since the
+ * texosquery.tex code is designed to be generic we can't assume
+ * the eTeX \\detokenize primitive is available. This does,
+ * however, assume that the document author hasn't changed the
+ * category codes of the ASCII alphanumerics, but that ought to
+ * be a safe assumption.
+ *
+ * We also have commands for characters intended for use in document
+ * text, which shouldn't be interpreted literally. These are all
+ * in the form \\txxx (such as \\thsh which should expand to
+ * \#).
+ *
+ * The regular space \\tspc guards against a space occurring after
+ * a character that needs to be converted to a control sequence.
+ * (For example "# 1" becomes "\\thsh \\tspc 1")
+ * There's also a literal space \\fspc to guard against spaces
+ * in file names.
+ *
+ * This should take care of any insane file-naming schemes, such
+ * as <tt>bad~file name#1.tex</tt>, <tt>stupid {file} name.tex</tt>,
+ * <tt style="white-space: pre;">spaced out file #2.tex</tt>,
+ * <tt>file's stupid name.tex</tt>.
+ *
+ * To help protect against input encoding problems, non-ASCII
+ * characters are wrapped in \\twrp (regular text) or \\fwrp
+ * (file names). \\TeXOSQuery locally redefines these to
+ * \\texosquerynonasciiwrap and \\texosquerynonasciidetokwrap
+ * which may be used to provide some protection or conversion from one
+ * encoding to another, if required.
+ *
+ * For example, the language "fran&#231;ais" would be returned as
+ * "fran\\twrp{&#231;}ais", which can be typeset directly with
+ * XeTeX or LuaTeX or through active characters with
+ * inputenc.sty, but the directory called <tt>Fran&#231;cois</tt> would be
+ * returned as <tt>Fran\\fwrp{&#231;}cois</tt>, which will try to
+ * detokenize the &#231; character.
+ *
+ * @param string Input string.
+ * @param isRegularText true if the string represents text (for example,
+ * month names), set to false if string is something literal,
+ * such as a file name.
+ * @return The processed string
+ * @since 1.2
+ */
+ public String escapeSpChars(String string, boolean isRegularText)
+ {
+ if (compatible < 2)
+ {
+ return escapeHash(string);
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ // This iterates over Unicode characters so we can't use a simple
+ // i++ increment. The offset is obtained from Character.charCount
+ for (int i = 0, n = string.length(); i < n; )
+ {
+ int codepoint = string.codePointAt(i);
+ i += Character.charCount(codepoint);
+
+ builder.append(escapeSpChars(codepoint, isRegularText));
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Escapes file name. This should already have had the directory
+ * divider changed to a forward slash where necessary.
+ * @param filename Input string.
+ * @return String with characters escaped.
+ * @since 1.2
+ */
+ public String escapeFileName(String filename)
+ {
+ return escapeSpChars(filename, false);
+ }
+
+ /**
+ * Escapes regular text.
+ * @param string Input string.
+ * @return String with characters escaped.
+ * @since 1.2
+ */
+ public String escapeText(String string)
+ {
+ return escapeSpChars(string, true);
+ }
+
+ /**
+ * Escapes regular text.
+ * @param codepoint Input Unicode character.
+ * @return String with characters escaped.
+ * @since 1.2
+ */
+ public String escapeText(int codepoint)
+ {
+ return escapeSpChars(codepoint, true);
+ }
+
+ /**
+ * Escapes the given Unicode character.
+ * All ASCII punctuation characters have a literal and textual
+ * command to represent them in file names and document text,
+ * respectively. The literal (file name) commands are prefixed
+ * with "f" and the textual commands are prefixed with "t".
+ * None of the control codes should appear in any of the
+ * results, but they are checked for completeness.
+ * @param codePoint Input code point.
+ * @param isRegularText true if the character is in a string representing
+ * text, set to false if string is a file name etc
+ * @return String with character escaped.
+ * @since 1.2
+ */
+ public String escapeSpChars(int codepoint, boolean isRegularText)
+ {
+ return escapeSpChars(codepoint, isRegularText ? "t" : "f");
+ }
+
+ /**
+ * Escapes the given Unicode character.
+ * As above but with the prefix supplied.
+ * @param codePoint Input code point.
+ * @param prefix The control sequence name prefix.
+ * @return String with character escaped.
+ * @since 1.2
+ */
+ public String escapeSpChars(int codepoint, String prefix)
+ {
+ switch (codepoint)
+ {
+ case '!': return String.format("\\%sexc ", prefix);
+ case '"': return String.format("\\%sdqt ", prefix);
+ case '#': return String.format("\\%shsh ", prefix);
+ case '$': return String.format("\\%sdol ", prefix);
+ case '%': return String.format("\\%spct ", prefix);
+ case '&': return String.format("\\%samp ", prefix);
+ case '\'': return String.format("\\%sapo ", prefix);
+ case '(': return String.format("\\%sopb ", prefix);
+ case ')': return String.format("\\%sclb ", prefix);
+ case '*': return String.format("\\%sast ", prefix);
+ case '+': return String.format("\\%spls ", prefix);
+ case ',': return String.format("\\%scom ", prefix);
+ case '-': return String.format("\\%shyn ", prefix);
+ case '.': return String.format("\\%sdot ", prefix);
+ case '/': return String.format("\\%sslh ", prefix);
+ case ':': return String.format("\\%scln ", prefix);
+ case ';': return String.format("\\%sscl ", prefix);
+ case '<': return String.format("\\%sles ", prefix);
+ case '=': return String.format("\\%seql ", prefix);
+ case '>': return String.format("\\%sgre ", prefix);
+ case '?': return String.format("\\%sque ", prefix);
+ case '@': return String.format("\\%satc ", prefix);
+ case '[': return String.format("\\%sosb ", prefix);
+ case '\\': return String.format("\\%sbks ", prefix);
+ case ']': return String.format("\\%scsb ", prefix);
+ case '^': return String.format("\\%scir ", prefix);
+ case '_': return String.format("\\%susc ", prefix);
+ case '`': return String.format("\\%sgrv ", prefix);
+ case '{': return String.format("\\%slbr ", prefix);
+ case '}': return String.format("\\%srbr ", prefix);
+ case '~': return String.format("\\%stld ", prefix);
+ case ' ': return String.format("\\%sspc ", prefix);
+ // These next few cases shouldn't occur, but
+ // check for them anyway.
+ case 0x007F: return ""; // delete control
+ case 0x0009: return "^^I";// tab
+ case 0x000A: // lf (fall through to cr)
+ case 0x000C: // ff
+ case 0x000D: return " "; // cr
+ default:
+
+ if (codepoint < 32)
+ {
+ return ""; // strip control characters
+ }
+ else if (codepoint >= 32 && codepoint <= 126)
+ {
+ // ASCII letters and digits (all ASCII punctuation
+ // dealt with above).
+ return String.format("%c", codepoint);
+ }
+ else
+ {
+ // Outside Basic Latin set.
+ return String.format("\\%swrp{%c}", prefix, codepoint);
+ }
+ }
+ }
+
+ /**
+ * Escapes any hashes in input string.
+ * Now only used if compatibility level is less than 2 (pre
+ * texosquery version 1.2).
+ * @param string Input string.
+ * @return String with hash escaped.
+ */
+ public static String escapeHash(String string)
+ {
+ return string.replaceAll("#", "\\\\#");
+ }
+
+ /**
+ * Escapes hash from input character.
+ * No longer required.
+ * @param c Input character.
+ * @return String with hash escaped.
+ */
+ public static String escapeHash(char c)
+ {
+ return String.format("%s", c == '#' ? "\\#" : c);
+ }
+
+ /**
+ * Gets the OS name. As far as I can tell, the "os.name"
+ * property should return a string that just contains Basic
+ * Latin upper or lower case letters, so we don't need to worry
+ * about special characters.
+ * @return The OS name as string.
+ */
+ public String getOSname()
+ {
+ return getSystemProperty("os.name", "");
+ }
+
+ /**
+ * Gets the OS architecture. As with the OS name, this shouldn't
+ * contain any special characters.
+ * @return The OS architecture as string.
+ */
+ public String getOSarch()
+ {
+ return getSystemProperty("os.arch", "");
+ }
+
+ /**
+ * Gets the OS version. This may contain an underscore, so treat
+ * it like a file name.
+ * @return The OS version as string.
+ */
+ public String getOSversion()
+ {
+ return escapeFileName(getSystemProperty("os.version", ""));
+ }
+
+ /**
+ * Converts the filename string to TeX path. Since this is designed to work
+ * within TeX, backslashes in paths need to be replaced with forward
+ * slashes.
+ * @param filename The filename string.
+ * @return TeX path.
+ */
+ public String toTeXPath(String filename)
+ {
+ if (filename == null)
+ {
+ // This shouldn't happen, but just in case...
+ try
+ {
+ // throw so we can get a stack trace for debugging
+ throw new NullPointerException();
+ }
+ catch (NullPointerException e)
+ {
+ debug("null file name", e);
+ }
+
+ return "";
+ }
+
+ // If the OS uses backslash as the directory divider,
+ // convert all backslashes to forward slashes. The Java regex
+ // means that we need four backslashes to represent a single literal
+ // backslash.
+
+ if (File.separatorChar == BACKSLASH)
+ {
+ filename = filename.replaceAll("\\\\", "/");
+ }
+
+ // Does a prefix need stripping?
+
+ if (stripFilePrefix != null && filename.startsWith(stripFilePrefix))
+ {
+ filename = filename.substring(stripFilePrefix.length());
+ }
+ else if (pathRegExp != null && pathReplacement != null)
+ {
+ filename = filename.replaceFirst(pathRegExp, pathReplacement);
+ }
+
+ return escapeFileName(filename);
+ }
+
+ /**
+ * Converts the TeX path to the OS representation.
+ * The file name will typically be passed as a parameter through
+ * \\TeXOSQuery so it will have forward slashes as the directory
+ * divider regardless of the OS (as per \\input and
+ * \\includegraphics). This method converts the TeX file name
+ * into one that's valid for the OS.
+ * @param filename The filename string.
+ * @return The OS representation.
+ */
+ public String fromTeXPath(String filename)
+ {
+ if (filename == null)
+ {
+ // This shouldn't happen, but just in case...
+ try
+ {
+ throw new NullPointerException();
+ }
+ catch (NullPointerException e)
+ {
+ debug("null file name", e);
+ }
+
+ return "";
+ }
+
+ if (compatible < 2)
+ {
+ if (File.separatorChar == BACKSLASH)
+ {
+ return filename.replaceAll("/", "\\\\");
+ }
+
+ return filename;
+ }
+
+ // The file name may contain awkward characters. For example,
+ // the user may have a file called imagefile#1.png and
+ // they're trying to do, say,
+ // \TeXOSQuery{\result}{-p imagefile#1.png}
+ // If the shell escape is using bash, the hash will be
+ // interpreted as a comment character, so the argument
+ // received by texosquery will actually be "imagefile"
+ // since the "#1.png" part will be interpreted as a comment.
+
+ // The user can protect the # from the shell using
+ // \TeXOSQuery{\result}{-p imagefile\string\#1.png}
+ // which bash will pass as 'imagefile#1.png', but
+ // perhaps another type of shell might pass it literally
+ // as 'imagefile\#1.png', so the following allows for
+ // that by simply stripping all backslashes from the file name.
+ // (The file name is always supplied with forward slashes as
+ // the directory divider regardless of the operating system.)
+ // We can substitute the divider at this point as well.
+
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0, n = filename.length(), offset=1; i < n; i+=offset)
+ {
+ int codepoint = filename.codePointAt(i);
+ offset = Character.charCount(codepoint);
+
+ int nextIndex = i+offset;
+ int nextCodePoint = (nextIndex<n ? filename.codePointAt(nextIndex):0);
+
+ if (codepoint == '/')
+ {
+ builder.appendCodePoint(File.separatorChar);
+ }
+ if (codepoint == BACKSLASH)
+ {
+ // Would anyone really want a literal backslash in a
+ // file name? Allow a double backslash to represent a
+ // literal backslash but only if the OS directory
+ // divider isn't a backslash. Otherwise discard
+ // this character.
+
+ if (File.separatorChar != BACKSLASH && nextCodePoint == BACKSLASH)
+ {
+ builder.appendCodePoint(codepoint);
+ i = nextIndex;
+ offset = Character.charCount(nextCodePoint);
+ }
+ else if (nextCodePoint == '/')
+ {
+ // Would anyone want a literal forward slash? Allow a
+ // slash to be escaped just in case.
+ builder.appendCodePoint('/');
+ i = nextIndex;
+ offset = Character.charCount(nextCodePoint);
+ }
+ }
+ else
+ {
+ builder.appendCodePoint(codepoint);
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Gets a file representation from a filename string. If the
+ * provided file doesn't have a parent and if it's not found in the
+ * current directory, kpsewhich will be used to locate it on
+ * TeX's path. The provided file name is assumed to have been
+ * passed through commands provided by texosquery.tex so the
+ * directory dividers should be forward slashes, even if the OS
+ * uses backslashes. The returned file may not exist. Any method
+ * that uses this method needs to check for existence.
+ * @param filename Filename string.
+ * @return File representation
+ * @since 1.2
+ */
+ public File fileFromTeXPath(String filename)
+ {
+ // Convert from TeX to the OS path representation.
+ filename = fromTeXPath(filename);
+
+ File file = new File(filename);
+
+ if (!file.exists() && file.getParent() == null)
+ {
+ // If the file doesn't exist and it doesn't have a parent
+ // directory, use kpsewhich to find it.
+
+ try
+ {
+ String result = kpsewhich(filename);
+
+ if (result != null && !"".equals(result))
+ {
+ file = new File(fromTeXPath(result));
+ }
+ }
+ catch (Exception exception)
+ {
+ // Catch all exceptions
+ debug(String.format("kpsewhich couldn't find the file: %s",
+ filename),
+ exception);
+
+ // The File object will be returned even though the file
+ // can't be found.
+ }
+ }
+
+ return file;
+ }
+
+ /**
+ * Gets the user's home directory.
+ * @return The user home as string.
+ */
+ public String getUserHome()
+ {
+ File dir = new File(getSystemProperty("user.home", ""));
+
+ if (!isReadPermitted(dir))
+ {
+ debug("Read access not permitted for the home directory");
+ return "";
+ }
+
+ // The resulting path needs to be converted to a TeX path.
+ return toTeXPath(dir.getAbsolutePath());
+ }
+
+ /**
+ * Gets the current working directory.
+ * @return The current working directory.
+ */
+ public String getCwd()
+ {
+ File dir = new File(getSystemProperty("user.dir", "."));
+
+ if (!isReadPermitted(dir))
+ {
+ // perhaps the current directory is hidden?
+ debug("Read access not permitted for the current directory");
+ return "";
+ }
+
+ // The resulting path needs to be converted to a TeX path.
+ return toTeXPath(dir.getAbsolutePath());
+ }
+
+ /**
+ * Gets the temporary directory.
+ * @return Temporary directory.
+ */
+ public String getTmpDir()
+ {
+ String filename = getSystemProperty("java.io.tmpdir", "");
+
+ if ("".equals(filename))
+ {
+ // Not set
+ return "";
+ }
+
+ File dir = new File(filename);
+
+ if (!isReadPermitted(dir))
+ {
+ debug(String.format("Read access not permitted for directory: %s",
+ dir));
+ return "";
+ }
+
+ // The resulting path needs to be converted to a TeX path.
+ return toTeXPath(filename);
+ }
+
+ /**
+ * Gets the week year for the given calendar.
+ * Calendar.getWeekYear() was added to Java 7, so this defaults
+ * to the year instead. This method needs to be overridden in
+ * TeXOSQueryJRE7 and TeXOSQueryJRE8.
+ * @return The week year
+ * @since 1.2
+ */
+ public int getWeekYear(Calendar cal)
+ {
+ return cal.get(Calendar.YEAR);
+ }
+
+ /**
+ * Converts the day of week index returned by
+ * Calendar.DAY_OF_WEEK to Monday=1 based indexing.
+ * @param index the day of week index obtained from Calendar.DAY_OF_WEEK
+ * @return index with Monday=1 as the base
+ * @since 1.2
+ */
+ private int getDayOfWeekIndex(int index)
+ {
+ switch (index)
+ {
+ case Calendar.MONDAY: return 1;
+ case Calendar.TUESDAY: return 2;
+ case Calendar.WEDNESDAY: return 3;
+ case Calendar.THURSDAY: return 4;
+ case Calendar.FRIDAY: return 5;
+ case Calendar.SATURDAY: return 6;
+ case Calendar.SUNDAY: return 7;
+ }
+
+ try
+ {
+ // this shouldn't happen
+ throw new IllegalArgumentException(
+ String.format("Invalid day of week index: %d", index));
+ }
+ catch (Exception e)
+ {
+ debug(e.getMessage(), e);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Gets all the date-time data for the current date-time.
+ * @return data in format that can be read by \\texosqueryfmtdatetime
+ * @since 1.2
+ */
+ public String getDateTimeData()
+ {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(now.getTime());
+
+ int hourH = cal.get(Calendar.HOUR_OF_DAY);
+
+ int hourk = (hourH == 0 ? 24 : hourH);
+
+ int hourK = cal.get(Calendar.HOUR);
+
+ int hourh = (hourK == 0 ? 12 : hourK);
+
+ TimeZone timeZone = cal.getTimeZone();
+ boolean isDaylightSaving = timeZone.inDaylightTime(now);
+
+ int timezoneoffset = cal.get(Calendar.ZONE_OFFSET);
+
+ if (isDaylightSaving)
+ {
+ timezoneoffset += cal.get(Calendar.DST_OFFSET);
+ }
+
+ // convert from offset millisec to hours and minutes
+ // (ignore left-over seconds and milliseconds)
+
+ int tzm = timezoneoffset/60000;
+
+ int tzh = tzm/60;
+
+ tzm = tzm % 60;
+
+ return String.format(
+ "{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{%d}{{%d}{%d}{%s}{%d}}",
+ cal.get(Calendar.ERA),
+ cal.get(Calendar.YEAR),
+ getWeekYear(cal),
+ cal.get(Calendar.MONTH)+1,
+ cal.get(Calendar.WEEK_OF_YEAR),
+ cal.get(Calendar.WEEK_OF_MONTH),
+ cal.get(Calendar.DAY_OF_YEAR),
+ cal.get(Calendar.DAY_OF_MONTH),
+ cal.get(Calendar.DAY_OF_WEEK_IN_MONTH),
+ getDayOfWeekIndex(cal.get(Calendar.DAY_OF_WEEK)),// Monday=1, etc
+ cal.get(Calendar.AM_PM),
+ hourH, hourk, hourK, hourh,
+ cal.get(Calendar.MINUTE),
+ cal.get(Calendar.SECOND),
+ cal.get(Calendar.MILLISECOND),
+ tzh, tzm, timeZone.getID(),
+ isDaylightSaving ? 1 : 0);
+ }
+
+ /**
+ * Get the time zone names for the given locale.
+ * The data for each zone is provided in the form
+ * {id}{short name}{long name}{short dst name}\marg{long dst name}
+ * @param localeTag The locale
+ * @return list of zone information for the locale
+ * @since 1.2
+ */
+
+ public String getTimeZones(String localeTag)
+ {
+ Locale locale;
+
+ if (localeTag == null || "".equals(localeTag))
+ {
+ locale = Locale.getDefault();
+ }
+ else
+ {
+ locale = getLocale(localeTag);
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ String[] zones = TimeZone.getAvailableIDs();
+
+ for (int i = 0; i < zones.length; i++)
+ {
+ TimeZone tz = TimeZone.getTimeZone(zones[i]);
+
+ builder.append(String.format("{{%s}{%s}{%s}{%s}{%s}}",
+ escapeFileName(tz.getID()),
+ escapeText(tz.getDisplayName(false, TimeZone.SHORT, locale)),
+ escapeText(tz.getDisplayName(false, TimeZone.LONG, locale)),
+ escapeText(tz.getDisplayName(true, TimeZone.SHORT, locale)),
+ escapeText(tz.getDisplayName(true, TimeZone.LONG, locale))));
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Gets the current date in PDF format. (The same format as
+ * \pdfcreationdate.)
+ * @return The current date.
+ */
+ public String pdfnow()
+ {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(now.getTime());
+
+ return pdfDate(cal);
+ }
+
+ /**
+ * Gets the date in PDF format.
+ * @param calendar A calendar object.
+ * @return Date in PDF format.
+ */
+ public String pdfDate(Calendar calendar)
+ {
+ String tz = String.format("%1$tz", calendar);
+
+ if (compatible < 2)
+ {
+ return String.format(
+ "D:%1$tY%1$tm%1td%1$tH%1$tM%1$tS%2$s'%3$s'",
+ calendar,
+ tz.substring(0, 3),
+ tz.substring(3));
+ }
+ else
+ {
+ // Need to ensure D : + or - and ' have category code 12
+ // The simplest way to deal with this is to pass
+ // everything after the "D" to escapeFileName since
+ // the sign is hidden in the format.
+
+ return String.format("\\pdfd %s",
+ escapeFileName(
+ String.format(
+ ":%1$tY%1$tm%1td%1$tH%1$tM%1$tS%2$s'%3$s'",
+ calendar,
+ tz.substring(0, 3),
+ tz.substring(3))
+ ));
+
+ }
+ }
+
+ /**
+ * Gets the date of a file in PDF format.
+ * @param file File.
+ * @return The date in PDF format.
+ */
+ public String pdfDate(File file)
+ {
+ try
+ {
+ if (!file.exists())
+ {
+ debug(String.format(
+ "Unable to get timestamp for file (no such file): %s",
+ file.toString()));
+ return "";
+ }
+
+ if (!isReadPermitted(file))
+ {
+ debug(String.format("No read access for file: %s", file));
+ return "";
+ }
+
+ long millisecs = file.lastModified();
+
+ if (millisecs > ZERO)
+ {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(millisecs);
+
+ return pdfDate(calendar);
+ }
+
+ // I/O error has occurred (already checked for file
+ // existence and read permission, so it's something weird).
+ // Perhaps the file is corrupt or the user has an eccentric OS that
+ // doesn't support file modification timestamps.
+ debug(String.format(
+ "Unable to get timestamp for file (I/O error): %s",
+ file.toString()));
+ }
+ catch (Exception exception)
+ {
+ // Catch all possible exceptions, including security
+ // exception.
+
+ debug(String.format(
+ "Unable to get timestamp for file: %s",
+ file.toString()),
+ exception);
+ }
+
+ // Unsuccessful
+ return "";
+ }
+
+ /**
+ * Gets the file length in bytes.
+ * @param file The file.
+ * @return The length as a string.
+ */
+ public String getFileLength(File file)
+ {
+ try
+ {
+ if (!file.exists())
+ {
+ debug(String.format(
+ "Unable to get the size of file (no such file): %s",
+ file.toString()));
+ return "";
+ }
+
+ if (!isReadPermitted(file))
+ {
+ debug(String.format("No read access for file: %s", file));
+ return "";
+ }
+
+ return String.format("%d", file.length());
+
+ }
+ catch (Exception exception)
+ {
+ // Catch all possible exceptions, including security
+ // exceptions.
+
+ debug(String.format("Unable to get the size of file: %s",
+ file.toString()),
+ exception);
+ }
+
+ // Unsuccessful
+ return "";
+ }
+
+ /**
+ * Sort the given list of file names. Java 8 has a better sort
+ * method so this is overridden in the TeXOSQueryJRE8 class.
+ * @param list The list of file names to be sorted
+ * @param directory The directory in which the files are
+ * contained
+ * @param sortType How to order the list
+ */
+ public void sortFileList(String[] list, File directory,
+ FileSortType sortType)
+ {
+ Arrays.sort(list, new FileSortComparator(directory, sortType));
+ }
+
+ /**
+ * Checks the directory used for file listings. The JRE5 version
+ * just returns the argument. The other versions convert the
+ * directory to a canonical path and check it's permitted. (The
+ * JRE7 and 8 versions are more restrictive.)
+ */
+ protected File checkDirectoryListing(File dir) throws IOException
+ {
+ return dir;
+ }
+
+ /**
+ * Gets the list of files from a directory. This uses
+ * getFilterFileList to filter out files prohibited by the
+ * openin_any setting. Note that the separator isn't escaped as
+ * the user may want some actual TeX code. For example, the
+ * separator might need to be a double backslash.
+ * The user will need to take the appropriate precautions
+ * to protect it from expansion during the shell escape.
+ * @param separator Separator.
+ * @param directory Directory (root not permitted).
+ * @param sortType How to sort the file list
+ * @param listType The type of files to include in the list
+ * @return List as a string.
+ */
+ public String getFileList(String separator, File directory,
+ FileSortType sortType, FileListType listType)
+ {
+ return getFilterFileList(separator, ".*", directory, sortType, listType);
+ }
+
+ /**
+ * Gets a filtered list of files from directory.
+ * Files with read access prohibited by openin_any or the OS are
+ * omitted from the list. The regular expression is anchored,
+ * so ".*foo" will only match file names that end with "foo".
+ *
+ * For security reasons, as from v1.2, the directory must have a
+ * parent (otherwise malicious code could try to perform a
+ * recursive search across the filing system, which would hog
+ * resources). To allow for backward compatibility, the insecure
+ * JRE5 version doesn't have this new restriction.
+ *
+ * @param separator Separator.
+ * @param regex Regular expression.
+ * @param directory Directory.
+ * @param sortType How to sort the file list
+ * @param listType The type of files to include in the list
+ * @return Filtered list as string.
+ */
+ public String getFilterFileList(String separator,
+ final String regex, File directory,
+ FileSortType sortType, final FileListType listType)
+ {
+ if (directory == null)
+ {
+ // shouldn't happen, but just in case...
+
+ debug("Unable to list contents (null directory)");
+ return "";
+ }
+
+ // Check for existence and that the given File is actually a directory.
+
+ if (!directory.exists())
+ {
+ debug(String.format(
+ "Unable to list contents (no such directory): %s",
+ directory.toString()));
+ return "";
+ }
+
+ if (!directory.isDirectory())
+ {
+ debug(String.format(
+ "Unable to list contents (not a directory): %s",
+ directory.toString()));
+ return "";
+ }
+
+ try
+ {
+ // security check (converts to full canonical path with JRE7 or 8)
+
+ directory = checkDirectoryListing(directory);
+ }
+ catch (Exception e)
+ {
+ debug(String.format("Unable to list contents of: %s",
+ directory.getAbsolutePath()), e);
+ return "";
+ }
+
+ if (!isReadPermitted(directory))
+ {
+ debug(String.format("No read access for directory: %s", directory));
+ return "";
+ }
+
+ if ((regex == null) || ("".equals(regex)))
+ {
+ // null or empty regular expression forbidden (use ".*" for
+ // all files, "" means only match a file with an empty
+ // filename, which doesn't make much sense).
+
+ debug("Null or empty regular expression in getFilterFileList");
+ return "";
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ try
+ {
+ String[] list = directory.list(
+ new FilenameFilter()
+ {
+ @Override
+ public boolean accept(File dir, String name)
+ {
+ File file = new File(dir, name);
+
+ if (!isReadPermitted(file))
+ {
+ debug(String.format("No read access for file: %s", file));
+ return false;
+ }
+
+ switch (listType)
+ {
+ case FILE_LIST_DIRECTORIES_ONLY:
+
+ if (!file.isDirectory()) return false;
+
+ break;
+ case FILE_LIST_REGULAR_FILES_ONLY:
+
+ if (file.isDirectory()) return false;
+
+ break;
+ }
+
+ return name.matches(regex);
+ }
+ });
+
+ if (list != null)
+ {
+ if (sortType != FileSortType.FILE_SORT_DEFAULT)
+ {
+ sortFileList(list, directory, sortType);
+ }
+
+ for (int i = 0; i < list.length; i++)
+ {
+ if (i > 0)
+ {
+ builder.append(separator);
+ }
+
+ if (list[i].contains(separator))
+ {
+ builder.append(String.format("{%s}", escapeFileName(list[i])));
+ }
+ else
+ {
+ builder.append(escapeFileName(list[i]));
+ }
+ }
+
+ }
+
+ return builder.toString();
+ }
+ catch (Exception exception)
+ {
+ // Catch all possible exceptions
+ debug(String.format("Unable to list contents of '%s' using regex: %s",
+ directory.toString(), regex),
+ exception);
+ }
+
+ // Unsuccessful
+ return "";
+ }
+
+ /**
+ * Recursive file listing. This method must have the CWD or a
+ * descendent as the starting directory. It will return list of
+ * files relative to the starting directory where the basename
+ * matches the supplied regular expression. Hidden files/directories
+ * and symbolic links are skipped regardless of the openin_any setting.
+ * Files without read access are also omitted from the list.
+ *
+ * This method requires the java.nio.file library, which was
+ * introduced in Java 7, so this isn't available for the JRE5
+ * version.
+ *
+ * @param separator separator to use in returned list
+ * @param regex regular expression used to match file basenames
+ * @param directory starting directory (must be cwd or a
+ * descendent of cwd)
+ * @return list of relative paths
+ */
+ public String walk(String separator,
+ String regex, File directory,
+ FileSortType sortType)
+ {
+ debug("walk requires at least JRE 7 version");
+ return "";
+ }
+
+ /**
+ * Gets the file URI.
+ * @param file The file.
+ * @return The URI.
+ */
+ public String fileURI(File file)
+ {
+ if (file == null)
+ {
+ // This shouldn't happen, but just in case...
+ debug("null file passed to fileURI");
+ return "";
+ }
+
+ if (!file.exists())
+ {
+ debug(String.format("can't obtain URI of file (no such file): %s",
+ file.toString()));
+ return "";
+ }
+
+ if (!isReadPermitted(file))
+ {
+ debug(String.format("No read access for file: %s", file));
+ return "";
+ }
+
+ try
+ {
+ String uri = file.getCanonicalFile().toURI().toString();
+
+ if (stripURIPrefix != null && uri.startsWith("file:/"+stripURIPrefix))
+ {
+ uri = "file:/"+uri.substring(6+stripURIPrefix.length());
+ }
+ else if (uriRegExp != null && uriReplacement != null)
+ {
+ uri = uri.replaceFirst(uriRegExp, uriReplacement);
+ }
+
+ return escapeFileName(uri);
+ }
+ catch (Exception exception)
+ {
+ debug(String.format("Can't obtain URI of file: %s", file.toString()),
+ exception);
+ }
+
+ // Unsuccessful
+ return "";
+ }
+
+ /**
+ * Gets the full TeX file path name from File object.
+ * @param file The file.
+ * @return The path.
+ */
+ public String filePath(File file)
+ {
+ if (file == null)
+ {
+ // This shouldn't happen, but just in case...
+ debug("null file passed to filePath");
+ return "";
+ }
+
+ if (!file.exists())
+ {
+ debug(String.format(
+ "Can't obtain full file path (no such file): %s",
+ file.toString()));
+ return "";
+ }
+
+ if (!isReadPermitted(file))
+ {
+ debug(String.format(
+ "Can't obtain full file path (no read access): %s",
+ file.toString()));
+ return "";
+ }
+
+ try
+ {
+ return toTeXPath(file.getCanonicalPath());
+ }
+ catch (Exception exception)
+ {
+ debug(String.format(
+ "Can't obtain full path for file: %s", file.toString()),
+ exception);
+ }
+
+ // Unsuccessful
+ return "";
+ }
+
+ /**
+ * Gets the path for the file's parent.
+ * @param file The file.
+ * @return The path.
+ * @since 1.1
+ */
+ public String parentPath(File file)
+ {
+ if (file == null)
+ {
+ // This shouldn't happen, but just in case...
+ debug("null file passed to filePath");
+ return "";
+ }
+
+ if (!file.exists())
+ {
+ debug(String.format(
+ "Can't obtain full parent path for file (no such file): %s",
+ file.toString()));
+ return "";
+ }
+
+ if (!isReadPermitted(file))
+ {
+ debug(String.format(
+ "Can't obtain full path for file (no read access): %s",
+ file.toString()));
+ return "";
+ }
+
+ try
+ {
+ File parent = file.getCanonicalFile().getParentFile();
+
+ if (parent == null)
+ {
+ // No parent? If getCanonicalFile fails it throws an
+ // exception, so no parent would presumably mean the
+ // file's in the root directory.
+
+ debug(String.format(
+ "No parent found for file: %s", file.toString()));
+ return "";
+ }
+
+ return toTeXPath(parent.getAbsolutePath());
+
+ }
+ catch (Exception exception)
+ {
+ debug(String.format(
+ "Can't obtain full parent path for file: %s", file.toString()),
+ exception);
+ }
+
+ // Unsuccessful
+ return "";
+ }
+
+ /**
+ * Gets the script for the given locale. Java only introduced
+ * support for language scripts in version 1.7, so this returns
+ * null here. The Java 7 and 8 support needs to override this method.
+ * @param locale The locale
+ * @return The language script associated with the given locale or
+ * null if not available
+ * @since 1.2
+ */
+ public String getScript(Locale locale)
+ {
+ return null;
+ }
+
+ /**
+ * Gets the language tag for the given locale.
+ * @param locale The locale or null for the default locale
+ * @return The language tag
+ * @since 1.2
+ */
+ public String getLanguageTag(Locale locale)
+ {
+ if (locale == null)
+ {
+ locale = Locale.getDefault();
+ }
+
+ String tag = locale.getLanguage();
+
+ String country = locale.getCountry();
+
+ if (country != null && !"".equals(country))
+ {
+ tag = String.format("%s-%s", tag, country);
+ }
+
+ String variant = locale.getVariant();
+
+ if (variant != null && !"".equals(variant))
+ {
+ tag = String.format("%s-%s", tag, variant);
+ }
+
+ return tag;
+ }
+
+ /**
+ * Gets a string representation of the provided locale.
+ * @param locale The provided locale.
+ * @return String representation.
+ */
+ public String getLocale(Locale locale)
+ {
+ return getLocale(locale, false);
+ }
+
+ /**
+ * Gets a POSIX representation of the provided locale, converting the code
+ * set if possible. If the boolean argument is true, this
+ * attempts to convert the code set to a identifier that stands
+ * a better chance of being recognised by inputenc.sty. For
+ * example, UTF-8 will be converted to utf8. None of TeX's
+ * special characters should occur in any of the locale
+ * information, but we'd better treat it like a file name just in
+ * case.
+ * @param locale The provided locale.
+ * @param convertCodeset Boolean value to convert the code set.
+ * @return String representation.
+ */
+ public String getLocale(Locale locale, boolean convertCodeset)
+ {
+ String identifier = "";
+
+ if (locale == null)
+ {
+ // No locale provided, return empty string
+ debug("null locale");
+ return "";
+ }
+
+ String language = locale.getLanguage();
+
+ if (language == null)
+ {
+ // No language provided for the locale. The language
+ // part will be omitted from the returned string.
+ debug(String.format("No language for locale: %s", locale.toString()));
+ }
+ else
+ {
+ identifier = language;
+ }
+
+ String country = locale.getCountry();
+
+ if (country == null || "".equals(country))
+ {
+ // No country is associated with the locale. The
+ // country part will be omitted from the returned
+ // string. This is just information, not an error.
+
+ info(String.format("No region for locale: %s", locale.toString()));
+ }
+ else
+ {
+ if ("".equals(identifier))
+ {
+ // The identifier hasn't been set (no language
+ // provided), so just set it to the country code.
+ identifier = country;
+ }
+ else
+ {
+ // Append the country code to the identifier.
+ identifier = identifier.concat("-").concat(country);
+ }
+ }
+
+ String codeset = getCodeSet(convertCodeset);
+
+ identifier = identifier.concat(".").concat(codeset);
+
+ // Find the script if available. This is used as the modifier part
+ // but it's better to use a language tag if the script is
+ // needed.
+
+ String script = getScript(locale);
+
+ if (script == null || "".equals(script))
+ {
+ // Script information is missing. Ignore it.
+ // This is just an advisory message.
+
+ info(String.format("No script available for locale: %s",
+ locale.toString()));
+ }
+ else
+ {
+ // Append the script. This will be a four letter string
+ // (if it's not empty).
+ identifier = identifier.concat("@").concat(script);
+ }
+
+ return escapeFileName(identifier);
+ }
+
+ /**
+ * Gets default file encoding. (Don't escape it here or it will cause
+ * a problem when called in getLocale.)
+ * @param convertCodeset If true convert codeset to fit
+ * inputenc.sty
+ * @return the file encoding.
+ * @since 1.2
+ */
+ public String getCodeSet(boolean convertCodeset)
+ {
+ String codeset;
+
+ if (fileEncoding != null)
+ {
+ codeset = fileEncoding;
+ }
+ else
+ {
+ // Get the OS default file encoding or "UTF-8" if not set.
+
+ codeset = getSystemProperty("file.encoding", "UTF-8");
+
+ // The codeset should not be null here as a default has
+ // been provided if the property is missing.
+ }
+
+ if (convertCodeset)
+ {
+ // If conversion is required, change to lower case
+ // and remove any hyphens.
+ codeset = codeset.toLowerCase().replaceAll("-", "");
+ }
+
+ return codeset;
+ }
+
+ /**
+ * Gets the two-letter alpha region code from the numeric code.
+ * (Java doesn't seem to recognise the numeric codes.)
+ * @param ISO 3166-1 numeric code
+ * @return ISO 3166-1 alpha code
+ * @since 1.2
+ */
+ public String getRegionAlpha2Code(int code)
+ {
+ switch (code)
+ {
+ case 4: return "AF";
+ case 8: return "AL";
+ case 10: return "AQ";
+ case 12: return "DZ";
+ case 16: return "AS";
+ case 20: return "AD";
+ case 24: return "AO";
+ case 28: return "AG";
+ case 31: return "AZ";
+ case 32: return "AR";
+ case 36: return "AU";
+ case 40: return "AT";
+ case 44: return "BS";
+ case 48: return "BH";
+ case 50: return "BD";
+ case 51: return "AM";
+ case 52: return "BB";
+ case 56: return "BE";
+ case 60: return "BM";
+ case 64: return "BT";
+ case 68: return "BO";
+ case 70: return "BA";
+ case 72: return "BW";
+ case 74: return "BV";
+ case 76: return "BR";
+ case 84: return "BZ";
+ case 86: return "IO";
+ case 90: return "SB";
+ case 92: return "VG";
+ case 96: return "BN";
+ case 100: return "BG";
+ case 104: return "MM";
+ case 108: return "BI";
+ case 112: return "BY";
+ case 116: return "KH";
+ case 120: return "CM";
+ case 124: return "CA";
+ case 132: return "CV";
+ case 136: return "KY";
+ case 140: return "CF";
+ case 144: return "LK";
+ case 148: return "TD";
+ case 152: return "CL";
+ case 156: return "CN";
+ case 158: return "TW";
+ case 162: return "CX";
+ case 166: return "CC";
+ case 170: return "CO";
+ case 174: return "KM";
+ case 175: return "YT";
+ case 178: return "CG";
+ case 180: return "CD";
+ case 184: return "CK";
+ case 188: return "CR";
+ case 191: return "HR";
+ case 192: return "CU";
+ case 196: return "CY";
+ case 203: return "CZ";
+ case 204: return "BJ";
+ case 208: return "DK";
+ case 212: return "DM";
+ case 214: return "DO";
+ case 218: return "EC";
+ case 222: return "SV";
+ case 226: return "GQ";
+ case 231: return "ET";
+ case 232: return "ER";
+ case 233: return "EE";
+ case 234: return "FO";
+ case 238: return "FK";
+ case 239: return "GS";
+ case 242: return "FJ";
+ case 246: return "FI";
+ case 248: return "AX";
+ case 250: return "FR";
+ case 254: return "GF";
+ case 258: return "PF";
+ case 260: return "TF";
+ case 262: return "DJ";
+ case 266: return "GA";
+ case 268: return "GE";
+ case 270: return "GM";
+ case 275: return "PS";
+ case 276: return "DE";
+ case 288: return "GH";
+ case 292: return "GI";
+ case 296: return "KI";
+ case 300: return "GR";
+ case 304: return "GL";
+ case 308: return "GD";
+ case 312: return "GP";
+ case 316: return "GU";
+ case 320: return "GT";
+ case 324: return "GN";
+ case 328: return "GY";
+ case 332: return "HT";
+ case 334: return "HM";
+ case 336: return "VA";
+ case 340: return "HN";
+ case 344: return "HK";
+ case 348: return "HU";
+ case 352: return "IS";
+ case 356: return "IN";
+ case 360: return "ID";
+ case 364: return "IR";
+ case 368: return "IQ";
+ case 372: return "IE";
+ case 376: return "IL";
+ case 380: return "IT";
+ case 384: return "CI";
+ case 388: return "JM";
+ case 392: return "JP";
+ case 398: return "KZ";
+ case 400: return "JO";
+ case 404: return "KE";
+ case 408: return "KP";
+ case 410: return "KR";
+ case 414: return "KW";
+ case 417: return "KG";
+ case 418: return "LA";
+ case 422: return "LB";
+ case 426: return "LS";
+ case 428: return "LV";
+ case 430: return "LR";
+ case 434: return "LY";
+ case 438: return "LI";
+ case 440: return "LT";
+ case 442: return "LU";
+ case 446: return "MO";
+ case 450: return "MG";
+ case 454: return "MW";
+ case 458: return "MY";
+ case 462: return "MV";
+ case 466: return "ML";
+ case 470: return "MT";
+ case 474: return "MQ";
+ case 478: return "MR";
+ case 480: return "MU";
+ case 484: return "MX";
+ case 492: return "MC";
+ case 496: return "MN";
+ case 498: return "MD";
+ case 499: return "ME";
+ case 500: return "MS";
+ case 504: return "MA";
+ case 508: return "MZ";
+ case 512: return "OM";
+ case 516: return "NA";
+ case 520: return "NR";
+ case 524: return "NP";
+ case 528: return "NL";
+ case 531: return "CW";
+ case 533: return "AW";
+ case 534: return "SX";
+ case 535: return "BQ";
+ case 540: return "NC";
+ case 548: return "VU";
+ case 554: return "NZ";
+ case 558: return "NI";
+ case 562: return "NE";
+ case 566: return "NG";
+ case 570: return "NU";
+ case 574: return "NF";
+ case 578: return "NO";
+ case 580: return "MP";
+ case 581: return "UM";
+ case 583: return "FM";
+ case 584: return "MH";
+ case 585: return "PW";
+ case 586: return "PK";
+ case 591: return "PA";
+ case 598: return "PG";
+ case 600: return "PY";
+ case 604: return "PE";
+ case 608: return "PH";
+ case 612: return "PN";
+ case 616: return "PL";
+ case 620: return "PT";
+ case 624: return "GW";
+ case 626: return "TL";
+ case 630: return "PR";
+ case 634: return "QA";
+ case 638: return "RE";
+ case 642: return "RO";
+ case 643: return "RU";
+ case 646: return "RW";
+ case 652: return "BL";
+ case 654: return "SH";
+ case 659: return "KN";
+ case 660: return "AI";
+ case 662: return "LC";
+ case 663: return "MF";
+ case 666: return "PM";
+ case 670: return "VC";
+ case 674: return "SM";
+ case 678: return "ST";
+ case 682: return "SA";
+ case 686: return "SN";
+ case 688: return "RS";
+ case 690: return "SC";
+ case 694: return "SL";
+ case 702: return "SG";
+ case 703: return "SK";
+ case 704: return "VN";
+ case 705: return "SI";
+ case 706: return "SO";
+ case 710: return "ZA";
+ case 716: return "ZW";
+ case 724: return "ES";
+ case 728: return "SS";
+ case 729: return "SD";
+ case 732: return "EH";
+ case 740: return "SR";
+ case 744: return "SJ";
+ case 748: return "SZ";
+ case 752: return "SE";
+ case 756: return "CH";
+ case 760: return "SY";
+ case 762: return "TJ";
+ case 764: return "TH";
+ case 768: return "TG";
+ case 772: return "TK";
+ case 776: return "TO";
+ case 780: return "TT";
+ case 784: return "AE";
+ case 788: return "TN";
+ case 792: return "TR";
+ case 795: return "TM";
+ case 796: return "TC";
+ case 798: return "TV";
+ case 800: return "UG";
+ case 804: return "UA";
+ case 807: return "MK";
+ case 818: return "EG";
+ case 826: return "GB";
+ case 831: return "GG";
+ case 832: return "JE";
+ case 833: return "IM";
+ case 834: return "TZ";
+ case 840: return "US";
+ case 850: return "VI";
+ case 854: return "BF";
+ case 858: return "UY";
+ case 860: return "UZ";
+ case 862: return "VE";
+ case 876: return "WF";
+ case 882: return "WS";
+ case 887: return "YE";
+ case 894: return "ZM";
+ }
+
+ // not recognised, return the code as a string
+ debug(String.format("Unrecognised numeric region code: %d", code));
+ return String.format("%d", code);
+ }
+
+ /**
+ * Gets the locale from the given language tag. Since Java didn't
+ * support BCP47 language tags until v1.7, we can't use
+ * Locale.forLanguageTag(String) here. (The Java 7 and 8 support
+ * will need to override this method.) Only parse for language
+ * code, country code and variant. Grandfathered, irregular and private
+ * tags not supported.
+ * @param languageTag The language tag
+ * @return The locale that closest matches the language tag
+ * @since 1.2
+ */
+ public Locale getLocale(String languageTag)
+ {
+ // The BCP47 syntax is described in
+ // https://tools.ietf.org/html/bcp47#section-2.1
+ // This is a match for a subset of the regular syntax.
+ // Only the language tag, the region and the variant are
+ // captured.
+ // Note: named capturing groups was introduced in Java 7, so we
+ // can't use them here.
+ Pattern p = Pattern.compile(
+ "(?:([a-z]{2,3}(?:-[a-z]{2,3})*))+(?:-[A-Z][a-z]{3})?(?:-([A-Z]{2}|[0-9]{3}))?(?:-([a-zA-Z0-9]{5,8}|[0-9][a-zA-Z0-9]{3}))?(?:-.)*");
+
+ Matcher m = p.matcher(languageTag);
+
+ if (m.matches())
+ {
+ String language = m.group(1);
+ String region = m.group(2);
+ String variant = m.group(3);
+
+ try
+ {
+ region = getRegionAlpha2Code(Integer.parseInt(region));
+ }
+ catch (NumberFormatException e)
+ {
+ // ignore, alpha region code was supplied
+ }
+
+ // Language won't be null as the pattern requires it, but
+ // the region and variant might be.
+
+ if (region == null)
+ {
+ // There isn't a Locale constructor that allows a
+ // variant without a region, so don't bother checking
+ // variant for null here.
+
+ return new Locale(language);
+ }
+
+ if (variant == null)
+ {
+ return new Locale(language, region);
+ }
+
+ return new Locale(language, region, variant);
+ }
+
+ debug(String.format("Can't parse language tag: %s", languageTag));
+
+ // strip anything to a hyphen and try that
+ String[] split = languageTag.split("-", 1);
+
+ return new Locale(split[0]);
+ }
+
+ /**
+ * Gets all numerical information for the given locale. If the
+ * given locale tag is null or empty, the default locale is used. The
+ * information is returned with each item grouped to make it
+ * easier to parse in TeX. This is an abridged version of
+ * getLocaleData().
+ * @param localeTag the tag identifying the locale or null for
+ * the default locale
+ * @return locale numerical information: language tag,
+ * number group separator, decimal separator, exponent separator,
+ * grouping conditional (1 if locale uses number grouping
+ * otherwise 0),
+ * currency code (e.g. GBP), regional currency identifier (e.g. IMP),
+ * currency symbol (e.g. \\twrp{&0x00A3;}), currency TeX code (e.g.
+ * \\texosquerycurrency{pound}), monetary decimal separator.
+ * @since 1.2
+ */
+ public String getNumericalInfo(String localeTag)
+ {
+ Locale locale;
+
+ if (localeTag == null || "".equals(localeTag))
+ {
+ locale = Locale.getDefault();
+ }
+ else
+ {
+ locale = getLocale(localeTag);
+ }
+
+ DecimalFormatSymbols fmtSyms
+ = DecimalFormatSymbols.getInstance(locale);
+
+ // ISO 4217 code
+ String currencyCode = fmtSyms.getInternationalCurrencySymbol();
+
+ // Currency symbol
+ String currency = fmtSyms.getCurrencySymbol();
+
+ // Check for known unofficial currency codes
+
+ String localeCurrencyCode = currencyCode;
+
+ String countryCode = locale.getCountry();
+
+ if (countryCode != null && !"".equals(countryCode))
+ {
+ if (countryCode.equals("GG") || countryCode.equals("GGY")
+ || countryCode.equals("831"))
+ {// Guernsey
+ localeCurrencyCode = "GGP";
+ currency = POUND_STRING;
+ }
+ else if (countryCode.equals("JE") || countryCode.equals("JEY")
+ || countryCode.equals("832"))
+ {// Jersey
+ localeCurrencyCode = "JEP";
+ currency = POUND_STRING;
+ }
+ else if (countryCode.equals("IM") || countryCode.equals("IMN")
+ || countryCode.equals("833"))
+ {// Isle of Man
+ localeCurrencyCode = "IMP";
+ currency = String.format("M%s", POUND_STRING);
+ }
+ else if (countryCode.equals("KI") || countryCode.equals("KIR")
+ || countryCode.equals("296"))
+ {// Kiribati
+ localeCurrencyCode = "KID";
+ currency = "$";
+ }
+ else if (countryCode.equals("TV") || countryCode.equals("TUV")
+ || countryCode.equals("798"))
+ {// Tuvaluan
+ localeCurrencyCode = "TVD";
+ currency = "$";
+ }
+ // Transnistrian ruble omitted as it conflicts with ISO
+ // 4217 so omitted. There's also no country code for
+ // Transnistria. Other currencies don't have an associated
+ // region code (for example, Somaliland) or don't have a
+ // known unofficial currency code (for example, Alderney).
+ }
+
+ // Convert known Unicode currency symbols to commands that
+ // may be redefined in TeX
+
+ String texCurrency = getTeXCurrency(currency);
+
+ NumberFormat numFormat = NumberFormat.getNumberInstance(locale);
+
+ // Currency codes should always be three letter upper case
+ // A-Z characters, so no need to escape them.
+
+ return String.format(
+ "{%s}{%s}{%s}{%s}{%d}{%s}{%s}{%s}{%s}{%s}",
+ escapeFileName(getLanguageTag(locale)),
+ escapeText(fmtSyms.getGroupingSeparator()),
+ escapeText(fmtSyms.getDecimalSeparator()),
+ escapeText(fmtSyms.getExponentSeparator()),
+ numFormat.isGroupingUsed() ? 1 : 0,
+ currencyCode,
+ localeCurrencyCode,
+ escapeText(currency),
+ texCurrency,// already escaped
+ escapeText(fmtSyms.getMonetaryDecimalSeparator()));
+ }
+
+ /**
+ * Gets the currency with known symbols replaced by TeX commands
+ * provided by texosquery.tex. Some of the conditions in this
+ * method test for archaic currency symbols. It seems very
+ * unlikely that any of those cases would actually occur, but
+ * they're included for completeness.
+ * @param currency The original currency string
+ * @return The TeX version
+ * @since 1.2
+ */
+ public String getTeXCurrency(String currency)
+ {
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0, n = currency.length(); i < n; )
+ {
+ int codepoint = currency.codePointAt(i);
+ i += Character.charCount(codepoint);
+
+ switch (codepoint)
+ {
+ case DOLLAR_CHAR:
+ builder.append("\\texosquerycurrency{dollar}");
+ break;
+ case CENT_CHAR:
+ builder.append("\\texosquerycurrency{cent}");
+ break;
+ case POUND_CHAR:
+ builder.append("\\texosquerycurrency{pound}");
+ break;
+ case CURRENCY_CHAR:
+ builder.append("\\texosquerycurrency{sign}");
+ break;
+ case YEN_CHAR:
+ builder.append("\\texosquerycurrency{yen}");
+ break;
+ case ECU_CHAR:
+ builder.append("\\texosquerycurrency{ecu}");
+ break;
+ case COLON_CURRENCY_CHAR:
+ builder.append("\\texosquerycurrency{colon}");
+ break;
+ case CRUZEIRO_CHAR:
+ builder.append("\\texosquerycurrency{cruzeiro}");
+ break;
+ case FRANC_CHAR:
+ builder.append("\\texosquerycurrency{franc}");
+ break;
+ case LIRA_CHAR:
+ builder.append("\\texosquerycurrency{lira}");
+ break;
+ case MILL_CURRENCY_CHAR:
+ builder.append("\\texosquerycurrency{mill}");
+ break;
+ case NAIRA_CHAR:
+ builder.append("\\texosquerycurrency{naira}");
+ break;
+ case PESETA_CHAR:
+ builder.append("\\texosquerycurrency{peseta}");
+ break;
+ case LEGACY_RUPEE_CHAR:
+ case RUPEE_CHAR:
+ builder.append("\\texosquerycurrency{rupee}");
+ break;
+ case WON_CHAR:
+ builder.append("\\texosquerycurrency{won}");
+ break;
+ case NEW_SHEQEL_CHAR:
+ builder.append("\\texosquerycurrency{newsheqel}");
+ break;
+ case DONG_CHAR:
+ builder.append("\\texosquerycurrency{dong}");
+ break;
+ case EURO_CHAR:
+ builder.append("\\texosquerycurrency{euro}");
+ break;
+ case KIP_CHAR:
+ builder.append("\\texosquerycurrency{kip}");
+ break;
+ case TUGRIK_CHAR:
+ builder.append("\\texosquerycurrency{tugrik}");
+ break;
+ case DRACHMA_CHAR:
+ builder.append("\\texosquerycurrency{drachma}");
+ break;
+ case GERMAN_PENNY_CHAR:
+ builder.append("\\texosquerycurrency{germanpenny}");
+ break;
+ case PESO_CHAR:
+ builder.append("\\texosquerycurrency{peso}");
+ break;
+ case GUARANI_CHAR:
+ builder.append("\\texosquerycurrency{guarani}");
+ break;
+ case AUSTRAL_CHAR:
+ builder.append("\\texosquerycurrency{austral}");
+ break;
+ case HRYVNIA_CHAR:
+ builder.append("\\texosquerycurrency{hryvnia}");
+ break;
+ case CEDI_CHAR:
+ builder.append("\\texosquerycurrency{cedi}");
+ break;
+ case LIVRE_TOURNOIS_CHAR:
+ builder.append("\\texosquerycurrency{livretournois}");
+ break;
+ case SPESMILO_CHAR:
+ builder.append("\\texosquerycurrency{spesmilo}");
+ break;
+ case TENGE_CHAR:
+ builder.append("\\texosquerycurrency{tenge}");
+ break;
+ case TURKISH_LIRA_CHAR:
+ builder.append("\\texosquerycurrency{turkishlira}");
+ break;
+ case NORDIC_MARK_CHAR:
+ builder.append("\\texosquerycurrency{nordicmark}");
+ break;
+ case MANAT_CHAR:
+ builder.append("\\texosquerycurrency{manat}");
+ break;
+ case RUBLE_CHAR:
+ builder.append("\\texosquerycurrency{ruble}");
+ break;
+ default:
+ builder.append(escapeText(codepoint));
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /** Gets the standalone month names for the locale data.
+ * These are only available for Java 8, so just return the
+ * month names used in the date format instead. The JRE8 version
+ * needs to override this method.
+ * @param cal The calendar
+ * @param locale The locale
+ * @return month names
+ * @since 1.2
+ */
+ public String getStandaloneMonths(Calendar cal, Locale locale)
+ {
+ // can't use Calendar.getDisplayName() as it's not available
+ // with Java 5.
+ DateFormatSymbols dateFmtSyms = DateFormatSymbols.getInstance(locale);
+
+ StringBuilder monthNamesGroup = new StringBuilder();
+
+ String[] names = dateFmtSyms.getMonths();
+
+ for (int i = 0; i < 12; i++)
+ {
+ monthNamesGroup.append(String.format("{%s}", escapeText(names[i])));
+ }
+
+ return monthNamesGroup.toString();
+ }
+
+ /** Gets the standalone short month names for the locale data.
+ * These are only available for Java 8, so just return the
+ * month names used in the date format instead. The JRE8 version
+ * needs to override this method.
+ * @param cal The calendar
+ * @param locale The locale
+ * @return short month names
+ * @since 1.2
+ */
+ public String getStandaloneShortMonths(Calendar cal, Locale locale)
+ {
+ // can't use Calendar.getDisplayName() as it's not available
+ // with Java 5.
+ DateFormatSymbols dateFmtSyms = DateFormatSymbols.getInstance(locale);
+
+ StringBuilder shortMonthNamesGroup = new StringBuilder();
+
+ String[] names = dateFmtSyms.getShortMonths();
+
+ for (int i = 0; i < 12; i++)
+ {
+ shortMonthNamesGroup.append(String.format("{%s}",
+ escapeText(names[i])));
+ }
+
+ return shortMonthNamesGroup.toString();
+ }
+
+ /** Gets the standalone day names for the locale data.
+ * These are only available for Java 8, so just return the
+ * names used in the date format instead. The JRE8 version
+ * needs to override this method.
+ * @param cal The calendar
+ * @param locale The locale
+ * @return day of week names
+ * @since 1.2
+ */
+ public String getStandaloneWeekdays(Calendar cal, Locale locale)
+ {
+ DateFormatSymbols dateFmtSyms = DateFormatSymbols.getInstance(locale);
+
+ String[] names = dateFmtSyms.getWeekdays();
+
+ // Be consistent with pgfcalendar:
+
+ return String.format("{%s}{%s}{%s}{%s}{%s}{%s}{%s}",
+ escapeText(names[Calendar.MONDAY]),
+ escapeText(names[Calendar.TUESDAY]),
+ escapeText(names[Calendar.WEDNESDAY]),
+ escapeText(names[Calendar.THURSDAY]),
+ escapeText(names[Calendar.FRIDAY]),
+ escapeText(names[Calendar.SATURDAY]),
+ escapeText(names[Calendar.SUNDAY]));
+ }
+
+ /** Gets the standalone short day names for the locale data.
+ * These are only available for Java 8, so just return the
+ * names used in the date format instead. The JRE8 version
+ * needs to override this method.
+ * @param cal The calendar
+ * @param locale The locale
+ * @return day of week names
+ * @since 1.2
+ */
+ public String getStandaloneShortWeekdays(Calendar cal, Locale locale)
+ {
+ DateFormatSymbols dateFmtSyms = DateFormatSymbols.getInstance(locale);
+
+ String[] names = dateFmtSyms.getShortWeekdays();
+
+ // Be consistent with pgfcalendar:
+
+ return String.format("{%s}{%s}{%s}{%s}{%s}{%s}{%s}",
+ escapeText(names[Calendar.MONDAY]),
+ escapeText(names[Calendar.TUESDAY]),
+ escapeText(names[Calendar.WEDNESDAY]),
+ escapeText(names[Calendar.THURSDAY]),
+ escapeText(names[Calendar.FRIDAY]),
+ escapeText(names[Calendar.SATURDAY]),
+ escapeText(names[Calendar.SUNDAY]));
+ }
+
+ /**
+ * Converts date/time pattern to a form that's easier for TeX to
+ * parse. This replaces the placeholders with <tt>\\patdtf{n}{c}</tt> where c
+ * is the placeholder character and n is the number of
+ * occurrences of c in the placeholder. (For example,
+ * "<tt>dd-MMM-yyyy</tt>" is converted to
+ * <tt>\\patdtf{2}{d}-\\patdtf{3}{M}-\\patdtf{4}{y}</tt>). The
+ * query command \\TeXOSQuery in texosquery.tex will expand \\patdtf
+ * to the longer \\texosquerydtf to avoid conflict. This can then be
+ * redefined as appropriate. (See the "Pattern Formats" section
+ * of the TeX documented code for more detail.)
+ * @param localeFormat The date/time pattern
+ * @return TeX code
+ * @since 1.2
+ */
+ public String formatDateTimePattern(Format localeFormat)
+ {
+ SimpleDateFormat fmt = null;
+
+ try
+ {
+ fmt = (SimpleDateFormat)localeFormat;
+
+ if (fmt == null)
+ {
+ throw new NullPointerException();
+ }
+ }
+ catch (Exception e)
+ {
+ // this shouldn't happen
+ debug(String.format("Invalid argument: %s", localeFormat), e);
+ return "";
+ }
+
+ String pattern = fmt.toPattern();
+
+ StringBuilder builder = new StringBuilder();
+
+ int prev = 0;
+ int fieldLen = 0;
+ boolean inString = false;
+
+ for (int i = 0, n = pattern.length(), offset=1; i < n; i = i+offset)
+ {
+ int codepoint = pattern.codePointAt(i);
+ offset = Character.charCount(codepoint);
+
+ int nextIndex = i+offset;
+ int nextCodePoint = (nextIndex < n ? pattern.codePointAt(nextIndex):0);
+
+ if (inString)
+ {
+ if (codepoint == '\'')
+ {
+ if (nextCodePoint != '\'')
+ {
+ // reached the end of the string
+ builder.append('}');
+ inString = false;
+ }
+ else
+ {
+ // literal '
+ builder.append("\\patapo ");
+ i = nextIndex;
+ offset = Character.charCount(nextCodePoint);
+ }
+ }
+ else
+ {
+ // still inside the string
+ builder.append(escapeText(codepoint));
+ }
+ }
+ else if (codepoint == prev)
+ {
+ fieldLen++;
+ }
+ else
+ {
+ switch (codepoint)
+ {
+ case '\'': // quote
+
+ if (prev != 0)
+ {
+ builder.append(String.format(
+ "\\patdtf{%d}{%c}", fieldLen, prev));
+ prev = 0;
+ fieldLen = 0;
+ }
+
+ // start of the string
+ builder.append("\\patstr{");
+ inString = true;
+
+ break;
+ case 'G': // era
+ case 'y': // year
+ case 'Y': // week year
+ case 'M': // month in year (context sensitive)
+ case 'L': // month in year (standalone)
+ case 'w': // week in year
+ case 'W': // week in month
+ case 'D': // day in year
+ case 'd': // day in month
+ case 'F': // day of week in month
+ case 'E': // day name in week
+ case 'u': // day number of week (1 = Mon)
+ case 'a': // am/pm marker
+ case 'H': // hour in day (0-23)
+ case 'k': // hour in day (1-24)
+ case 'K': // hour in am/pm (0-11)
+ case 'h': // hour in am/pm (1-12)
+ case 'm': // minute in hour
+ case 's': // second in minute
+ case 'S': // millisecond
+ case 'z': // time zone (locale)
+ case 'Z': // time zone (RFC 822)
+ case 'X': // time zone (ISO 8601)
+ prev = codepoint;
+ fieldLen = 1;
+ break;
+ default:
+ // prev doesn't need escaping as it will be one
+ // of the above letter cases.
+
+ if (prev == 0)
+ {
+ builder.append(escapeText(codepoint));
+ }
+ else
+ {
+ builder.append(String.format(
+ "\\patdtf{%d}{%c}%s", fieldLen, prev,
+ escapeText(codepoint)));
+ }
+ prev = 0;
+ fieldLen = 0;
+ }
+ }
+ }
+
+ if (prev != 0)
+ {
+ builder.append(String.format(
+ "\\patdtf{%d}{%c}", fieldLen, prev));
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Converts numeric pattern to a form that's easier for TeX to parse.
+ * @param numFormat the numeric pattern
+ * @return TeX code
+ * @since 1.2
+ */
+ public String formatNumberPattern(Format numFormat)
+ {
+ DecimalFormat fmt = null;
+
+ try
+ {
+ fmt = (DecimalFormat)numFormat;
+
+ if (fmt == null)
+ {
+ throw new NullPointerException();
+ }
+ }
+ catch (Exception e)
+ {
+ // this shouldn't happen
+ debug(String.format("Invalid argument: %s", numFormat), e);
+ return "";
+ }
+
+ String pattern = fmt.toPattern();
+
+ // Is there a +ve;-ve sub-pattern pair?
+ // This is a bit awkward as a semi-colon could appear
+ // literally within a string.
+
+ String positive = null;
+ String negative = null;
+
+ StringBuilder builder = new StringBuilder();
+ boolean inString = false;
+
+ for (int i = 0, n = pattern.length(), offset=1; i < n; i = i+offset)
+ {
+ int codepoint = pattern.codePointAt(i);
+ offset = Character.charCount(codepoint);
+
+ int nextIndex = i+offset;
+ int nextCodePoint = (nextIndex < n ? pattern.codePointAt(nextIndex):0);
+
+ if (inString)
+ {
+ if (codepoint == '\'')
+ {
+ builder.appendCodePoint(codepoint);
+
+ if (nextCodePoint == '\'')
+ {
+ // literal '
+ builder.appendCodePoint(nextCodePoint);
+ i = nextIndex;
+ offset = Character.charCount(nextCodePoint);
+ }
+ else
+ {
+ inString = false;
+ }
+ }
+ else
+ {
+ builder.appendCodePoint(codepoint);
+ }
+ }
+ else if (codepoint == '\'')
+ {
+ inString = true;
+ builder.appendCodePoint(codepoint);
+ }
+ else if (codepoint == ';')
+ {
+ if (positive == null)
+ {
+ positive = builder.toString();
+ builder = new StringBuilder();
+ }
+ else
+ {
+ debug(String.format("Too many ; found in pattern: %s",
+ pattern));
+ }
+ }
+ else
+ {
+ builder.appendCodePoint(codepoint);
+ }
+ }
+
+ if (positive == null)
+ {
+ positive = builder.toString();
+ }
+ else if (builder.length() > 0)
+ {
+ negative = builder.toString();
+ }
+
+ if (negative == null)
+ {
+ return String.format("\\patnumfmt{%s}",
+ formatNumberSubPattern(positive));
+ }
+ else
+ {
+ return String.format("\\patpmnumfmt{%s}{%s}",
+ formatNumberSubPattern(positive),
+ formatNumberSubPattern(negative));
+ }
+ }
+
+ /**
+ * Converts the sub-pattern of a numeric format.
+ * @param pattern The sub-pattern
+ * @return TeX code
+ * @since 1.2
+ */
+ private String formatNumberSubPattern(String pattern)
+ {
+ if (pattern == null || "".equals(pattern))
+ {
+ return "";
+ }
+
+ // Is this currency?
+
+ Pattern p = Pattern.compile("(.*(?:[^'](?:'')+){0,1})("+CURRENCY_CHAR
+ +"{1,2})(.*)");
+ Matcher m = p.matcher(pattern);
+
+ if (m.matches())
+ {
+ return formatCurrencyPattern(m.group(1),
+ (m.group(2).length() == 2), m.group(3));
+ }
+
+ // Is this a percentage?
+
+ p = Pattern.compile("(.*(?:[^'](?:'')+){0,1})([%"+PERMILLE_CHAR+"])(.*)");
+ m = p.matcher(pattern);
+
+ if (m.matches())
+ {
+ boolean percent = ("%".equals(m.group(2)));
+
+ return formatPercentagePattern(m.group(1), m.group(3),
+ percent ? "patppct" : "patppml",
+ percent ? "patspct" : "patspml");
+ }
+
+ // must be a number
+
+ return formatNumericPattern(pattern);
+ }
+
+ /**
+ * Converts the currency format.
+ * @param pre The pre-symbol pattern
+ * @param international Determines if the international currency
+ * symbol should be used
+ * @param post The post-symbol pattern
+ * @return TeX code
+ * @since 1.2
+ */
+ private String formatCurrencyPattern(String pre, boolean international,
+ String post)
+ {
+ if (post == null || "".equals(post))
+ {
+ pre = formatNumericPattern(pre);
+
+ // currency symbol is a suffix
+ if (international)
+ {
+ return String.format("\\patsicur{%s}{}", pre);
+ }
+ else
+ {
+ return String.format("\\patscur{%s}{}", pre);
+ }
+ }
+ else if (pre == null || "".equals(pre))
+ {
+ // currency symbol is a prefix
+
+ post = formatNumericPattern(post);
+
+ if (international)
+ {
+ return String.format("\\patpicur{%s}{}", post);
+ }
+ else
+ {
+ return String.format("\\patpcur{%s}{}", post);
+ }
+ }
+ else
+ {
+ // What do we do here? If pre contains '#' or '0' assume
+ // a suffix currency otherwise a prefix currency.
+
+ pre = formatNumericPattern(pre);
+ post = formatNumericPattern(post);
+
+ if (pre.matches(".*[0#].*"))
+ {
+ // suffix, pre is the number and post is trailing
+ // text
+ if (international)
+ {
+ return String.format("\\patsicur{%s}{%s}", pre, post);
+ }
+ else
+ {
+ return String.format("\\patscur{%s}{%s}", pre, post);
+ }
+ }
+ else
+ {
+ // prefix, post is the number and pre is leading
+ // text
+ if (international)
+ {
+ return String.format("\\patpicur{%s}{%s}", post, pre);
+ }
+ else
+ {
+ return String.format("\\patpcur{%s}{%s}", post, pre);
+ }
+ }
+ }
+ }
+
+ /**
+ * Converts percentage format.
+ * @param pre The pre-symbol pattern
+ * @param post The post-symbol pattern
+ * @param prefixCs The control sequence name to use if the symbol is a
+ * prefix
+ * @param suffixCs The control sequence name to use if the symbol is a
+ * suffix
+ * @return TeX code
+ * @since 1.2
+ */
+ private String formatPercentagePattern(String pre, String post,
+ String prefixCs, String suffixCs)
+ {
+ if (post == null || "".equals(post))
+ {
+ pre = formatNumericPattern(pre);
+
+ // symbol is a suffix
+
+ return String.format("\\%s{%s}{}", suffixCs, pre);
+ }
+ else if (pre == null || "".equals(pre))
+ {
+ // symbol is a prefix
+
+ post = formatNumericPattern(post);
+
+ return String.format("\\%s{%s}{}", prefixCs, post);
+ }
+ else
+ {
+ pre = formatNumericPattern(pre);
+ post = formatNumericPattern(post);
+
+ if (pre.matches(".*[0#].*"))
+ {
+ // suffix, pre is the number and post is trailing
+ // text
+
+ return String.format("\\%s{%s}{%s}", suffixCs, pre, post);
+ }
+ else
+ {
+ // prefix, post is the number and pre is leading
+ // text
+
+ return String.format("\\%s{%s}{%s}", prefixCs, post, pre);
+ }
+ }
+ }
+
+ /**
+ * Converts the numeric format.
+ * @param pattern The sub-pattern
+ * @return TeX code
+ * @since 1.2
+ */
+ private String formatNumericPattern(String pattern)
+ {
+ if (pattern == null || "".equals(pattern))
+ {
+ return "";
+ }
+
+ // Split around exponent (if present)
+
+ Pattern p = Pattern.compile("(.*(?:[^'](?:'')+?){0,1})(E.*)?");
+ Matcher m = p.matcher(pattern);
+
+ if (!m.matches())
+ {
+ debug(String.format(
+ "Can't match number format sub-pattern '%s' against regexp: %s",
+ pattern, p));
+ return "";
+ }
+
+ String pre = m.group(1);
+ String post = m.group(2);
+
+ if (pre == null && post == null)
+ {
+ // empty pattern
+ return "";
+ }
+
+ if (post == null)
+ {
+ return formatDecimalPattern(pre);
+ }
+
+ return String.format("\\patsinumfmt{%s}{%s}",
+ formatDecimalPattern(pre),
+ formatIntegerPattern(post, true));
+ }
+
+ /**
+ * Converts a decimal pattern.
+ * @param pattern The pattern
+ * @return TeX code
+ * @since 1.2
+ */
+ private String formatDecimalPattern(String pattern)
+ {
+ // split on the decimal point (if present)
+
+ Pattern p = Pattern.compile("(.*?(?:[^'](?:'')){0,1})(?:\\.(.*))?");
+
+ Matcher m = p.matcher(pattern);
+
+ if (!m.matches())
+ {
+ debug(String.format(
+ "Can't match decimal pattern '%s' against regexp: %s",
+ pattern, p));
+ return "";
+ }
+
+
+ String pre = m.group(1);
+ String post = m.group(2);
+
+ if (pre == null && post == null)
+ {
+ // empty pattern
+ return "";
+ }
+
+ if (post == null)
+ {
+ return formatIntegerPattern(pre, true);
+ }
+
+ return String.format("\\patdecfmt{%s}{%s}",
+ formatIntegerPattern(pre, true),
+ formatIntegerPattern(post, false));
+ }
+
+
+ /**
+ * Convert an integer pattern. The aim here is to have a number
+ * formatting command defined in TeX that will be passed a number
+ * with either leading or trailing zeros padded to 10 digits.
+ * TeX can't handle numbers higher than 2147483647, so any digits
+ * in the pattern beyond that are discarded. This means defining
+ * a command that effectively takes 10 arguments (with a bit of
+ * trickery to get around the 9-arg maximum). Each digit can then
+ * be rendered using either \\patdgt (always display the digit)
+ * or \\patdgtnz (only display the digit if it isn't a
+ * non-significant zero).
+ *
+ * These short commands will be converted to longer ones that are
+ * less likely to cause conflict when \\TeXOSQuery is used.
+ * (See the "Pattern Formats" section of the documented code for
+ * more details.)
+ * @param pattern The pattern
+ * @param leadPadding Determines if leading padding needs taking
+ * into account
+ * @return TeX code
+ * @since 1.2
+ */
+ private String formatIntegerPattern(String pattern, boolean leadPadding)
+ {
+ boolean inString = false;
+
+ int digitCount = 0;
+ int groupCount = -1;
+
+ // count the number of digits
+
+ for (int i = 0, n = pattern.length(), offset=1; i < n; i = i+offset)
+ {
+ int codepoint = pattern.codePointAt(i);
+ offset = Character.charCount(codepoint);
+
+ int nextIndex = i+offset;
+ int nextCodePoint = (nextIndex < n ? pattern.codePointAt(nextIndex):0);
+
+ if (inString)
+ {
+ if (codepoint == '\'')
+ {
+ if (nextCodePoint != '\'')
+ {
+ inString = false;
+ i = nextIndex;
+ offset = Character.charCount(nextCodePoint);
+ }
+ }
+ }
+ else if (codepoint == '\'')
+ {
+ inString = true;
+ }
+ else if (codepoint == '#' || codepoint == '0')
+ {
+ digitCount++;
+
+ if (groupCount > -1) groupCount++;
+ }
+ else if (codepoint == ',')
+ {
+ groupCount=0;
+ }
+ }
+
+ int digitIndex = (leadPadding ? MAX_DIGIT_FORMAT : 0);
+
+ inString = false;
+
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0, n = pattern.length(), offset=1; i < n; i = i+offset)
+ {
+ int codepoint = pattern.codePointAt(i);
+ offset = Character.charCount(codepoint);
+
+ int nextIndex = i+offset;
+ int nextCodePoint = (nextIndex < n ? pattern.codePointAt(nextIndex):0);
+
+ switch (codepoint)
+ {
+ case '\'':
+
+ if (!inString)
+ {
+ inString = true;
+
+ builder.append("\\patstr{");
+ }
+ else if (nextCodePoint == '\'')
+ {
+ builder.append("\\patapo ");
+ i = nextIndex;
+ offset = Character.charCount(nextCodePoint);
+ }
+ else
+ {
+ builder.append("}");
+ inString = false;
+ }
+ break;
+ case '0':
+
+ if (leadPadding)
+ {
+ if (digitIndex > MAX_DIGIT_FORMAT)
+ {
+ // too many digit markers in the pattern,
+ // discard
+ }
+ else if (digitIndex > digitCount)
+ {
+ // not enough digit markers in the pattern
+ // pad with #
+
+ for ( ; digitIndex > digitCount; digitIndex--)
+ {
+ builder.append("\\patdgtnz ");
+
+ if (groupCount > 0 && ((digitIndex-1) % groupCount) == 0)
+ {
+ builder.append("\\patngp ");
+ }
+ }
+
+ builder.append("\\patdgt ");
+ }
+ else
+ {
+ builder.append("\\patdgt ");
+ }
+
+ digitIndex--;
+ }
+ else
+ {
+ digitIndex++;
+
+ if (digitIndex > MAX_DIGIT_FORMAT)
+ {
+ // too many digit markers in the pattern,
+ // discard
+ }
+ else if (digitIndex == digitCount)
+ {
+ builder.append("\\patdgt ");
+
+ // not enough digit markers in the pattern
+ // pad with #
+
+ for ( ; digitIndex < MAX_DIGIT_FORMAT; digitIndex++)
+ {
+ builder.append("\\patdgtnz ");
+ }
+ }
+ else
+ {
+ builder.append("\\patdgt ");
+ }
+ }
+ break;
+ case '#':
+
+ if (leadPadding)
+ {
+ if (digitIndex > MAX_DIGIT_FORMAT)
+ {
+ // too many digit markers in the pattern,
+ // discard
+ }
+ else if (digitIndex > digitCount)
+ {
+ // not enough digit markers in the pattern
+ // pad with #
+
+ for ( ; digitIndex > digitCount; digitIndex--)
+ {
+ builder.append("\\patdgtnz ");
+
+ if (groupCount > 0 && ((digitIndex-1) % groupCount) == 0)
+ {
+ builder.append("\\patngp ");
+ }
+ }
+
+ builder.append("\\patdgtnz ");
+ }
+ else
+ {
+ builder.append("\\patdgtnz ");
+ }
+
+ digitIndex--;
+ }
+ else
+ {
+ digitIndex++;
+
+ if (digitIndex > MAX_DIGIT_FORMAT)
+ {
+ // too many digit markers in the pattern,
+ // discard
+ }
+ else if (digitIndex == digitCount)
+ {
+ builder.append("\\patdgtnz ");
+
+ // not enough digit markers in the pattern
+ // pad with #
+
+ for ( ; digitIndex < MAX_DIGIT_FORMAT; digitIndex++)
+ {
+ builder.append("\\patdgtnz ");
+ }
+ }
+ else
+ {
+ builder.append("\\patdgtnz ");
+ }
+ }
+ break;
+ case '-':
+ builder.append("\\patmsg ");
+ break;
+ case ',':
+
+ if (digitIndex <= digitCount)
+ {
+ builder.append("\\patngp ");
+ }
+
+ break;
+ default:
+ builder.append(escapeText(codepoint));
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Gets all available data for the given locale. If the
+ * given locale tag is null, the default locale is used. The
+ * information is returned with grouping to make it
+ * easier to parse in TeX. (Each block is grouped, with each
+ * element within the block also grouped.)
+ *
+ * The standalone month names and day of week names are new
+ * to Java 8, so we can't use them for earlier versions.
+ * @param localeTag the language tag identifying the locale or null for
+ * the default locale
+ * @return locale data in grouped blocks:
+ * <ol>
+ * <li>language tag, language name, language name in given locale,
+ * country name, country name in given locale, variant name,
+ * variant name in given locale.
+ * <li> full date, long date, medium date, short date,
+ * first day of the week index.
+ * <li> full date, long date, medium date, short date patterns.
+ * <li> full time, long time, medium time, short time.
+ * <li> full time, long time, medium time, short time patterns.
+ * <li> weekday names.
+ * <li> short weekday names.
+ * <li> month names
+ * <li> short month names.
+ * <li> standalone week day names.
+ * <li> standalone short week day names.
+ * <li> standalone month names.
+ * <li> standalone short month names.
+ * <li> number group separator,
+ * decimal separator, exponent separator, grouping flag, ISO 4217 currency
+ * identifier (e.g. GBP), region currency identifier (usually the same as
+ * the ISO 4217 code, but may be an unofficial currency code, such as IMP),
+ * currency symbol (e.g. &0x00A3;), TeX currency symbol, monetary decimal
+ * separator, percent symbol, per mille symbol.
+ * <li> number format, integer format, currency format,
+ * percent format.
+ * </ol>
+ * @since 1.2
+ */
+ public String getLocaleData(String localeTag)
+ {
+ Locale locale;
+
+ if (localeTag == null || "".equals(localeTag))
+ {
+ locale = Locale.getDefault();
+ }
+ else
+ {
+ locale = getLocale(localeTag);
+ }
+
+ String languageName = locale.getDisplayLanguage();
+
+ if (languageName == null)
+ {
+ languageName = "";
+ }
+
+ String localeLanguageName = locale.getDisplayLanguage(locale);
+
+ if (localeLanguageName == null)
+ {
+ localeLanguageName = "";
+ }
+
+ String countryName = locale.getDisplayCountry();
+
+ if (countryName == null)
+ {
+ countryName = "";
+ }
+
+ String localeCountryName = locale.getDisplayCountry(locale);
+
+ if (localeCountryName == null)
+ {
+ localeCountryName = "";
+ }
+
+ String variantName = locale.getDisplayVariant();
+
+ if (variantName == null)
+ {
+ variantName = "";
+ }
+
+ String localeVariantName = locale.getDisplayVariant(locale);
+
+ if (localeVariantName == null)
+ {
+ localeVariantName = "";
+ }
+
+ String langRegionGroup = String.format("{%s}{%s}{%s}{%s}{%s}{%s}{%s}",
+ escapeFileName(getLanguageTag(locale)),
+ escapeText(languageName),
+ escapeText(localeLanguageName),
+ escapeText(countryName),
+ escapeText(localeCountryName),
+ escapeText(variantName),
+ escapeText(localeVariantName));
+
+ DateFormat dateFullFormat = DateFormat.getDateInstance(
+ DateFormat.FULL, locale);
+
+ DateFormat dateLongFormat = DateFormat.getDateInstance(
+ DateFormat.LONG, locale);
+
+ DateFormat dateMediumFormat = DateFormat.getDateInstance(
+ DateFormat.MEDIUM, locale);
+
+ DateFormat dateShortFormat = DateFormat.getDateInstance(
+ DateFormat.SHORT, locale);
+
+ DateFormat timeFullFormat = DateFormat.getTimeInstance(
+ DateFormat.FULL, locale);
+
+ DateFormat timeLongFormat = DateFormat.getTimeInstance(
+ DateFormat.LONG, locale);
+
+ DateFormat timeMediumFormat = DateFormat.getTimeInstance(
+ DateFormat.MEDIUM, locale);
+
+ DateFormat timeShortFormat = DateFormat.getTimeInstance(
+ DateFormat.SHORT, locale);
+
+ DateFormat dateTimeFullFormat = DateFormat.getDateTimeInstance(
+ DateFormat.FULL, DateFormat.FULL, locale);
+
+ DateFormat dateTimeLongFormat = DateFormat.getDateTimeInstance(
+ DateFormat.LONG, DateFormat.LONG, locale);
+
+ DateFormat dateTimeMediumFormat = DateFormat.getDateTimeInstance(
+ DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
+
+ DateFormat dateTimeShortFormat = DateFormat.getDateTimeInstance(
+ DateFormat.SHORT, DateFormat.SHORT, locale);
+
+ // first day of the week index consistent with pgfcalendar
+ // (0 = Monday, etc)
+ int firstDay = 0;
+
+ Calendar cal = Calendar.getInstance(locale);
+ cal.setTimeInMillis(now.getTime());
+
+ switch (cal.getFirstDayOfWeek())
+ {
+ case Calendar.MONDAY:
+ firstDay = 0;
+ break;
+ case Calendar.TUESDAY:
+ firstDay = 1;
+ break;
+ case Calendar.WEDNESDAY:
+ firstDay = 2;
+ break;
+ case Calendar.THURSDAY:
+ firstDay = 3;
+ break;
+ case Calendar.FRIDAY:
+ firstDay = 4;
+ break;
+ case Calendar.SATURDAY:
+ firstDay = 5;
+ break;
+ case Calendar.SUNDAY:
+ firstDay = 6;
+ break;
+ }
+
+ String dateGroup = String.format("{%s}{%s}{%s}{%s}{%d}",
+ escapeText(dateFullFormat.format(now)),
+ escapeText(dateLongFormat.format(now)),
+ escapeText(dateMediumFormat.format(now)),
+ escapeText(dateShortFormat.format(now)),
+ firstDay);
+
+ String dateFmtGroup = String.format("{%s}{%s}{%s}{%s}",
+ formatDateTimePattern(dateFullFormat),
+ formatDateTimePattern(dateLongFormat),
+ formatDateTimePattern(dateMediumFormat),
+ formatDateTimePattern(dateShortFormat));
+
+ String timeGroup = String.format("{%s}{%s}{%s}{%s}",
+ escapeText(timeFullFormat.format(now)),
+ escapeText(timeLongFormat.format(now)),
+ escapeText(timeMediumFormat.format(now)),
+ escapeText(timeShortFormat.format(now)));
+
+ String timeFmtGroup = String.format("{%s}{%s}{%s}{%s}",
+ formatDateTimePattern(timeFullFormat),
+ formatDateTimePattern(timeLongFormat),
+ formatDateTimePattern(timeMediumFormat),
+ formatDateTimePattern(timeShortFormat));
+
+ String dateTimeGroup = String.format("{%s}{%s}{%s}{%s}",
+ escapeText(dateTimeFullFormat.format(now)),
+ escapeText(dateTimeLongFormat.format(now)),
+ escapeText(dateTimeMediumFormat.format(now)),
+ escapeText(dateTimeShortFormat.format(now)));
+
+ String dateTimeFmtGroup = String.format("{%s}{%s}{%s}{%s}",
+ formatDateTimePattern(dateTimeFullFormat),
+ formatDateTimePattern(dateTimeLongFormat),
+ formatDateTimePattern(dateTimeMediumFormat),
+ formatDateTimePattern(dateTimeShortFormat));
+
+ DateFormatSymbols dateFmtSyms = DateFormatSymbols.getInstance(locale);
+
+ String[] names = dateFmtSyms.getWeekdays();
+
+ // Be consistent with pgfcalendar:
+
+ String weekdayNamesGroup = String.format(
+ "{%s}{%s}{%s}{%s}{%s}{%s}{%s}",
+ escapeText(names[Calendar.MONDAY]),
+ escapeText(names[Calendar.TUESDAY]),
+ escapeText(names[Calendar.WEDNESDAY]),
+ escapeText(names[Calendar.THURSDAY]),
+ escapeText(names[Calendar.FRIDAY]),
+ escapeText(names[Calendar.SATURDAY]),
+ escapeText(names[Calendar.SUNDAY]));
+
+ names = dateFmtSyms.getShortWeekdays();
+
+ String shortWeekdayNamesGroup = String.format(
+ "{%s}{%s}{%s}{%s}{%s}{%s}{%s}",
+ escapeText(names[Calendar.MONDAY]),
+ escapeText(names[Calendar.TUESDAY]),
+ escapeText(names[Calendar.WEDNESDAY]),
+ escapeText(names[Calendar.THURSDAY]),
+ escapeText(names[Calendar.FRIDAY]),
+ escapeText(names[Calendar.SATURDAY]),
+ escapeText(names[Calendar.SUNDAY]));
+
+ StringBuilder monthNamesGroup = new StringBuilder();
+
+ names = dateFmtSyms.getMonths();
+
+ for (int i = 0; i < 12; i++)
+ {
+ // skip 13th month (Calendar.UNDECIMBER)
+ monthNamesGroup.append(String.format("{%s}", escapeText(names[i])));
+ }
+
+ StringBuilder shortMonthNamesGroup = new StringBuilder();
+
+ names = dateFmtSyms.getShortMonths();
+
+ for (int i = 0; i < 12; i++)
+ {
+ shortMonthNamesGroup.append(String.format("{%s}",
+ escapeText(names[i])));
+ }
+
+ // Get numerical data (as with getNumericalInfo)
+ DecimalFormatSymbols fmtSyms
+ = DecimalFormatSymbols.getInstance(locale);
+
+ // ISO 4217 code
+ String currencyCode = fmtSyms.getInternationalCurrencySymbol();
+
+ // Currency symbol
+ String currency = fmtSyms.getCurrencySymbol();
+
+ // Check for known unofficial currency codes
+
+ String localeCurrencyCode = currencyCode;
+
+ String countryCode = locale.getCountry();
+
+ if (countryCode != null && !"".equals(countryCode))
+ {
+ if (countryCode.equals("GG") || countryCode.equals("GGY")
+ || countryCode.equals("831"))
+ {// Guernsey
+ localeCurrencyCode = "GGP";
+ currency = POUND_STRING;
+ }
+ else if (countryCode.equals("JE") || countryCode.equals("JEY")
+ || countryCode.equals("832"))
+ {// Jersey
+ localeCurrencyCode = "JEP";
+ currency = POUND_STRING;
+ }
+ else if (countryCode.equals("IM") || countryCode.equals("IMN")
+ || countryCode.equals("833"))
+ {// Isle of Man
+ localeCurrencyCode = "IMP";
+ currency = String.format("M%s", POUND_STRING);
+ }
+ else if (countryCode.equals("KI") || countryCode.equals("KIR")
+ || countryCode.equals("296"))
+ {// Kiribati
+ localeCurrencyCode = "KID";
+ currency = "$";
+ }
+ else if (countryCode.equals("TV") || countryCode.equals("TUV")
+ || countryCode.equals("798"))
+ {// Tuvaluan
+ localeCurrencyCode = "TVD";
+ currency = "$";
+ }
+ // Transnistrian ruble omitted as it conflicts with ISO
+ // 4217. There's also no country code for
+ // Transnistria. Other currencies don't have an associated
+ // region code (for example, Somaliland) or don't have a
+ // known unofficial currency code (for example, Alderney).
+ }
+
+ // Convert known Unicode currency symbols to commands that
+ // may be redefined in TeX
+
+ String texCurrency = getTeXCurrency(currency);
+
+ NumberFormat numFormat = NumberFormat.getNumberInstance(locale);
+ NumberFormat intFormat = NumberFormat.getIntegerInstance(locale);
+ NumberFormat curFormat = NumberFormat.getCurrencyInstance(locale);
+ NumberFormat pcFormat = NumberFormat.getPercentInstance(locale);
+
+ String numGroup = String.format(
+ "{%s}{%s}{%s}{%d}{%s}{%s}{%s}{%s}{%s}{%s}{%s}",
+ escapeText(fmtSyms.getGroupingSeparator()),
+ escapeText(fmtSyms.getDecimalSeparator()),
+ escapeText(fmtSyms.getExponentSeparator()),
+ numFormat.isGroupingUsed() ? 1 : 0,
+ currencyCode,
+ localeCurrencyCode,
+ escapeText(currency),
+ texCurrency,// already escaped
+ escapeText(fmtSyms.getMonetaryDecimalSeparator()),
+ escapeText(fmtSyms.getPercent()),
+ escapeText(fmtSyms.getPerMill()));
+
+ String numFmtGroup = String.format("{%s}{%s}{%s}{%s}",
+ formatNumberPattern(numFormat),
+ formatNumberPattern(intFormat),
+ formatNumberPattern(curFormat),
+ formatNumberPattern(pcFormat));
+
+ return String.format(
+ "{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}{%s}",
+ langRegionGroup,
+ dateGroup,
+ dateFmtGroup,
+ timeGroup,
+ timeFmtGroup,
+ dateTimeGroup,
+ dateTimeFmtGroup,
+ weekdayNamesGroup,
+ shortWeekdayNamesGroup,
+ monthNamesGroup,
+ shortMonthNamesGroup,
+ getStandaloneWeekdays(cal, locale),
+ getStandaloneShortWeekdays(cal, locale),
+ getStandaloneMonths(cal, locale),
+ getStandaloneShortMonths(cal, locale),
+ numGroup, numFmtGroup);
+ }
+
+ /**
+ * Prints the syntax usage.
+ */
+ protected void syntax()
+ {
+ System.out.println(String.format("Usage: %s [<options>] <actions>", name));
+
+ System.out.println();
+ System.out.println("Cross-platform OS query application");
+ System.out.println("for use with TeX's shell escape.");
+ System.out.println();
+ System.out.println("Each query displays the result in a single line.");
+ System.out.println("An empty string is printed if the requested");
+ System.out.println("information is unavailable or not permitted.");
+ System.out.println("Multiple actions group the results.");
+ System.out.println();
+ System.out.println("See the manual (texdoc texosquery) for further details.");
+
+ System.out.println();
+ System.out.println("Options:");
+ System.out.println();
+
+ System.out.println("-h or --help or -help");
+ System.out.println("\tDisplay this help message and exit.");
+ System.out.println();
+
+ System.out.println("-v or --version or -version");
+ System.out.println("\tDisplay version information and exit.");
+ System.out.println();
+
+ System.out.println("--nodebug");
+ System.out.println("\tNo debugging messages (default)");
+ System.out.println();
+
+ System.out.println("--debug [<n>] or -debug [<n>]");
+ System.out.println("\tDisplay debugging messages on STDOUT.");
+ System.out.println("\t<n> should be an integer:");
+ System.out.println("\t0: no debugging (same as --nodebug)");
+ System.out.println("\t1: basic debugging messages");
+ System.out.println("\t2: additionally display stack trace.");
+ System.out.println(String.format("\tIf omitted %d is assumed", DEFAULT_DEBUG_LEVEL));
+ System.out.println();
+
+ System.out.println("--compatible <n> or -compat <n>");
+ System.out.println("\tCompatibility setting.");
+ System.out.println("\t<n> should be \"latest\" (default) or an integer:");
+
+ for (int i = 0; i < DEFAULT_COMPATIBLE; i++)
+ {
+ System.out.println(String.format("\t%d: version 1.%d", i, i));
+ }
+
+ System.out.println();
+
+ System.out.println("--encoding <charset> or -enc <charset>");
+ System.out.println("\tSet the file encoding to <charset>.");
+
+ System.out.println();
+
+ System.out.println("--default-encoding or -defenc");
+ System.out.println("\tUse the default file encoding.");
+
+ System.out.println();
+
+ System.out.println("--strip-path-prefix <prefix> or -sp <prefix>");
+ System.out.println("\tStrip the given prefix from returned path names.");
+ System.out.println("\tCan't be used with --replace-path.");
+ System.out.println();
+
+ System.out.println("--nostrip-path-prefix");
+ System.out.println("\tCancel the effect of --strip-path-prefix.");
+ System.out.println();
+
+ System.out.println("--replace-path <regex> <replacement> or -rp <regex> <replacement>");
+ System.out.println("\tSubstitutes the first occurrence of <regex> with");
+ System.out.println("\t<replacement> in any returned path names.");
+ System.out.println("\tCan't be used with --strip-path-prefix.");
+ System.out.println();
+
+ System.out.println("--noreplace-path");
+ System.out.println("\tCancel the effect of --replace-path.");
+ System.out.println();
+
+ System.out.println("--strip-uri-prefix <prefix> or -su <prefix>");
+ System.out.println("\tReplace 'file:/<prefix>' with 'file:/'");
+ System.out.println("\tfrom returned URIs.");
+ System.out.println("\tCan't be used with --replace-uri.");
+ System.out.println();
+
+ System.out.println("--nostrip-uri-prefix");
+ System.out.println("\tCancel the effect of --strip-uri-prefix.");
+ System.out.println();
+
+ System.out.println("--replace-uri <regex> <replacement> or -ru <regex> <replacement>");
+ System.out.println("\tSubstitutes the first occurrence of <regex> with");
+ System.out.println("\t<replacement> in any returned URIs.");
+ System.out.println("\tCan't be used with --strip-uri-prefix.");
+ System.out.println();
+
+ System.out.println("--noreplace-uri");
+ System.out.println("\tCancel the effect of --replace-uri.");
+ System.out.println();
+
+ System.out.println();
+ System.out.println("General actions:");
+ System.out.println();
+
+ for (QueryAction action : AVAILABLE_ACTIONS)
+ {
+ if (action.getType() == QueryActionType.GENERAL_ACTION)
+ {
+ System.out.println(action.help());
+ }
+ }
+
+ System.out.println();
+ System.out.println("Locale actions:");
+ System.out.println();
+
+ for (QueryAction action : AVAILABLE_ACTIONS)
+ {
+ if (action.getType() == QueryActionType.LOCALE_ACTION)
+ {
+ System.out.println(action.help());
+ }
+ }
+
+ System.out.println();
+ System.out.println("File actions:");
+ System.out.println();
+ System.out.println("Paths should use / for the directory divider.");
+ System.out.println("TeX's openin_any setting is checked before attempting");
+ System.out.println("to access file information.");
+ System.out.println();
+
+ for (QueryAction action : AVAILABLE_ACTIONS)
+ {
+ if (action.getType() == QueryActionType.FILE_ACTION)
+ {
+ System.out.println(action.help());
+ }
+ }
+ }
+
+ /**
+ * Prints the version.
+ */
+ protected void version()
+ {
+ System.out.println(String.format("%s %s %s", name, VERSION_NUMBER,
+ VERSION_DATE));
+ System.out.println("Copyright 2016 Nicola Talbot");
+ System.out.println("License LPPL 1.3+ (http://ctan.org/license/lppl1.3)");
+ }
+
+ /**
+ * Prints the information with optional grouping.
+ * @param numActions Add grouping if number of actions &gt; 1
+ * @param info Information to print
+ * @since 1.2
+ */
+ protected void print(int numActions, String info)
+ {
+ if (compatible == 0)
+ {
+ // version 1.0 didn't use grouping
+ System.out.println(info);
+ }
+ else
+ {
+ if (numActions > 1)
+ {
+ System.out.println(String.format("{%s}", info));
+ }
+ else
+ {
+ System.out.println(info);
+ }
+ }
+ }
+
+ /**
+ * Find the action corresponding to the name (the command line
+ * switch). Once the action has been found, a copy must be
+ * returned since the same action may be used multiple times with
+ * different arguments.
+ * @param action The command line switch (either the short or long
+ * form)
+ * @return a copy of the predefined action or null if not found
+ * @since 1.2
+ */
+ private QueryAction getAction(String action)
+ {
+ for (int i = 0; i < AVAILABLE_ACTIONS.length; i++)
+ {
+ if (AVAILABLE_ACTIONS[i].isAction(action))
+ {
+ return AVAILABLE_ACTIONS[i].copy();
+ }
+ }
+
+ return null;
+ }
+
+ public static int parseArgVal(String[] args, int i, Object[] argVal)
+ {
+ String[] sp;
+
+ if (args[i].startsWith("--"))
+ {
+ sp = args[i].split("=", 2);
+ }
+ else
+ {
+ sp = new String[]{args[i]};
+ }
+
+ argVal[0] = sp[0];
+
+ if (sp.length == 2)
+ {
+ argVal[1] = sp[1];
+ return i;
+ }
+
+ if (i == args.length-1 || args[i+1].startsWith("-"))
+ {
+ argVal[1] = null;
+ return i;
+ }
+
+ argVal[1] = args[++i];
+
+ return i;
+ }
+
+ public static int parseArgInt(String[] args, int i, Object[] argVal)
+ {
+ i = parseArgVal(args, i, argVal);
+
+ if (argVal[1] != null)
+ {
+ try
+ {
+ argVal[1] = new Integer((String)argVal[1]);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Invalid '%s' value: %s", argVal[0], argVal[1]), e);
+ }
+ }
+
+ return i;
+ }
+
+ public static boolean isArg(String arg, String shortArg, String longArg)
+ {
+ return arg.equals("-"+shortArg) || arg.equals("--"+longArg)
+ || arg.startsWith("--"+longArg+"=");
+ }
+
+
+ public static boolean isArg(String arg, String longArg)
+ {
+ return arg.equals("--"+longArg) || arg.startsWith("--"+longArg+"=");
+ }
+
+ /**
+ * Process command line arguments. Options must come before
+ * actions. (The copied QueryAction objects retain the settings
+ * from the time of their creation.)
+ * @param args Command line arguments.
+ * @since 1.2
+ */
+ public void processArgs(String[] args)
+ {
+ Vector<QueryAction> actions = new Vector<QueryAction>();
+ Object[] argVal = new Object[2];
+
+ for (int i = 0; i < args.length; i++)
+ {
+ QueryAction action = getAction(args[i]);
+
+ if (action != null)
+ {
+ try
+ {
+ i = action.parseArgs(args, i)-1;
+ actions.add(action);
+ }
+ catch (IllegalArgumentException e)
+ {
+ debug(e.getMessage(), e);
+ throw e;
+ }
+ catch (Throwable e)
+ {
+ debug(e.getMessage(), e);
+ throw new IllegalArgumentException(e.getMessage(), e);
+ }
+ }
+ else if (args[i].equals("-h") || args[i].equals("--help")
+ || args[i].equals("-help"))
+ {
+ syntax();
+ System.exit(0);
+ }
+ else if (args[i].equals("-v") || args[i].equals("--version")
+ || args[i].equals("-version"))
+ {
+ version();
+ System.exit(0);
+ }
+ else if (args[i].equals("--nodebug"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ debugLevel = 0;
+ }
+ else if (isArg(args[i], "debug", "debug"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ i = parseArgInt(args, i, argVal);
+
+ if (argVal[1] == null)
+ {
+ debugLevel = DEFAULT_DEBUG_LEVEL;
+ }
+ else
+ {
+ debugLevel = ((Integer)argVal[1]).intValue();
+
+ if (debugLevel < 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Invalid debug level: %s", args[i]));
+ }
+ }
+ }
+ else if (isArg(args[i], "compat", "compatible"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ i = parseArgVal(args, i, argVal);
+
+ if (argVal[1] == null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "<level> expected after: %s", args[i]));
+ }
+
+ if (argVal[1].equals("latest"))
+ {
+ compatible = DEFAULT_COMPATIBLE;
+ }
+ else
+ {
+ try
+ {
+ compatible = Integer.parseInt((String)argVal[1]);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Invalid %s argument (\"latest\" or %d to %d required): %s",
+ argVal[0], 0, DEFAULT_COMPATIBLE, argVal[1]), e);
+ }
+ }
+ }
+ else if (isArg(args[i], "sp", "strip-path-prefix"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ if (pathRegExp != null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Option clash: %s and --replace-path", args[i]));
+ }
+
+ i = parseArgVal(args, i, argVal);
+
+ if (argVal[1] == null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "<prefix> expected after: %s", args[i]));
+ }
+
+ stripFilePrefix = (String)argVal[1];
+ }
+ else if (isArg(args[i], "nostrip-path-prefix"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ stripFilePrefix = null;
+ }
+ else if (isArg(args[i], "rp", "replace-path"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ if (stripFilePrefix != null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Option clash: --strip-path-prefix and %s", args[i]));
+ }
+
+ i = parseArgVal(args, i, argVal);
+
+ pathRegExp = (String)argVal[1];
+
+ if (pathRegExp == null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "<regex> <replacement> expected after: %s", args[i]));
+ }
+
+ i = parseArgVal(args, i, argVal);
+
+ pathReplacement = (String)argVal[1];
+
+ if (pathReplacement == null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "<replacement> expected after: %s %s", args[i], pathRegExp));
+ }
+
+ }
+ else if (isArg(args[i], "noreplace-path"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ pathRegExp = null;
+ pathReplacement = null;
+ }
+ else if (isArg(args[i], "su", "strip-uri-prefix"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ if (uriRegExp != null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Option clash: %s and --replace-uri", args[i]));
+ }
+
+ i = parseArgVal(args, i, argVal);
+
+ if (argVal[1] == null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "<prefix> expected after: %s", args[i]));
+ }
+
+ stripURIPrefix = (String)argVal[1];
+ }
+ else if (isArg(args[i], "nostrip-uri-prefix"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ stripURIPrefix = null;
+ }
+ else if (isArg(args[i], "ru", "replace-uri"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ if (stripURIPrefix != null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Option clash: --strip-uri-prefix and %s", args[i]));
+ }
+
+ i = parseArgVal(args, i, argVal);
+
+ uriRegExp = (String)argVal[1];
+
+ if (uriRegExp == null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "<regex> <replacement> expected after: %s", args[i]));
+ }
+
+ i = parseArgVal(args, i, argVal);
+
+ uriReplacement = (String)argVal[1];
+
+ if (uriReplacement == null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "<replacement> expected after: %s %s", args[i], uriRegExp));
+ }
+
+ }
+ else if (isArg(args[i], "noreplace-uri"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ uriRegExp = null;
+ uriReplacement = null;
+ }
+ else if (isArg(args[i], "enc", "encoding"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ i = parseArgVal(args, i, argVal);
+
+ if (argVal[1] == null)
+ {
+ throw new IllegalArgumentException(String.format(
+ "<charset> expected after: %s", args[i]));
+ }
+
+ fileEncoding = (String)argVal[1];
+ }
+ else if (isArg(args[i], "defenc", "default-encoding"))
+ {
+ if (actions.size() > 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "Options must come before actions. Found option: %s", args[i]));
+ }
+
+ fileEncoding = null;
+ }
+ else
+ {
+ throw new IllegalArgumentException(String.format(
+ "Unknown option: %s%nTry %s --help", args[i], name));
+ }
+ }
+
+ int numActions = actions.size();
+
+ if (numActions == 0)
+ {
+ throw new IllegalArgumentException(String.format(
+ "One or more actions required.%nTry %s --help", name));
+ }
+
+ if (fileEncoding != null)
+ {
+ // new to v1.6
+ try
+ {
+ // Change the encoding of STDOUT.
+ // This is done by setting STDOUT to the original system
+ // STDOUT (FileDescription.out) within a print stream that
+ // has the appropriate file encoding.
+
+ // (This is more useful that setting file.encoding when
+ // the Java virtual machine starts up as this can be done on
+ // a per-document basis. Otherwise it requires editing
+ // the script that invokes the JVM.)
+
+ PrintStream stream = new PrintStream(
+ new FileOutputStream(FileDescriptor.out),
+ true, // auto flush
+ fileEncoding);
+
+ System.setOut(stream);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new IllegalArgumentException("VM does not support encoding "
+ +fileEncoding, e);
+ }
+ }
+
+ for (QueryAction action : actions)
+ {
+ try
+ {
+ print(numActions, action.doAction(compatible));
+ }
+ catch (Throwable e)
+ {
+ // Any errors should've been picked up by the action,
+ // so this is most likely a runtime error that needs
+ // to be reported.
+
+ System.err.println("Fatal error: "+e.getMessage());
+
+ if (debugLevel < DEBUG_STACK_TRACE_LEVEL)
+ {
+ System.err.println(String.format(
+ "Use --debug %d to obtain stack trace",
+ DEBUG_STACK_TRACE_LEVEL));
+ }
+
+ debug(String.format("Action failed: %s", action.getInvocation()),
+ e);
+ System.exit(1);
+ }
+ }
+ }
+
+ private final QueryAction[] AVAILABLE_ACTIONS = new QueryAction[]
+ {
+ new QueryAction("cwd", "c", QueryActionType.FILE_ACTION,
+ "Display current working directory")
+ {
+ public String action()
+ {
+ return getCwd();
+ }
+ },
+ new QueryAction("userhome", "m", QueryActionType.FILE_ACTION,
+ "Display user's home directory")
+ {
+ public String action()
+ {
+ return getUserHome();
+ }
+ },
+ new QueryAction("tmpdir", "t", QueryActionType.FILE_ACTION,
+ "Display temporary directory")
+ {
+ public String action()
+ {
+ return getTmpDir();
+ }
+ },
+ new QueryAction("osname", "o", QueryActionType.GENERAL_ACTION,
+ "Display OS name")
+ {
+ public String action()
+ {
+ return getOSname();
+ }
+ },
+ new QueryAction("osversion", "r", QueryActionType.GENERAL_ACTION,
+ "Display OS version")
+ {
+ public String action()
+ {
+ return getOSversion();
+ }
+ },
+ new QueryAction("osarch", "a", QueryActionType.GENERAL_ACTION,
+ "Display OS architecture")
+ {
+ public String action()
+ {
+ return getOSarch();
+ }
+ },
+ new QueryAction("pdfnow", "n", QueryActionType.GENERAL_ACTION,
+ "Display current date-time in PDF format")
+ {
+ public String action()
+ {
+ return pdfnow();
+ }
+ },
+ new QueryAction("locale", "L", QueryActionType.LOCALE_ACTION,
+ "Display POSIX locale information")
+ {
+ public String action()
+ {
+ return getLocale(Locale.getDefault());
+ }
+ },
+ new QueryAction("locale-lcs", "l", QueryActionType.LOCALE_ACTION,
+ "Display POSIX style locale information with lower case codeset")
+ {
+ public String action()
+ {
+ return getLocale(Locale.getDefault(), true);
+ }
+ },
+ new QueryAction("codeset", "cs", QueryActionType.GENERAL_ACTION,
+ "Display the codeset", 2)
+ {// new to v1.6
+ public String action()
+ {
+ return escapeFileName(getCodeSet(false));
+ }
+ },
+ new QueryAction("codeset-lcs", "C", QueryActionType.GENERAL_ACTION,
+ "Lower case codeset with hyphens stripped", 2)
+ {
+ public String action()
+ {
+ return escapeFileName(getCodeSet(true));
+ }
+ },
+ new QueryAction("bcp47", "b", QueryActionType.LOCALE_ACTION,
+ "Display locale as BCP47 tag", 2)
+ {
+ public String action()
+ {
+ return escapeFileName(getLanguageTag(null));
+ }
+ },
+ new QueryAction("numeric", "N", 1, 0, "[locale]",
+ QueryActionType.LOCALE_ACTION,
+ "Display locale numeric information", 2)
+ {
+ public String action()
+ {
+ return getNumericalInfo(getOptionalArgument(0));
+ }
+ },
+ new QueryAction("locale-data", "D", 1, 0, "[locale]",
+ QueryActionType.LOCALE_ACTION,
+ "Display all available locale information", 2)
+ {
+ public String action()
+ {
+ return getLocaleData(getOptionalArgument(0));
+ }
+ },
+ new QueryAction("date-time", "M",
+ QueryActionType.GENERAL_ACTION,
+ "Display all the current date-time data", 2)
+ {
+ public String action()
+ {
+ return getDateTimeData();
+ }
+ },
+ new QueryAction("time-zones", "Z", 1, 0, "[locale]",
+ QueryActionType.LOCALE_ACTION,
+ "Display all available time zone information", 2)
+ {
+ public String action()
+ {
+ return getTimeZones(getOptionalArgument(0));
+ }
+ },
+ new QueryAction("pdfdate", "d", 0, 1, "<file>",
+ QueryActionType.FILE_ACTION,
+ "Display date stamp of <file> in PDF format")
+ {
+ public String action()
+ {
+ return pdfDate(fileFromTeXPath(getRequiredArgument(0)));
+ }
+ },
+ new QueryAction("filesize", "s", 0, 1, "<file>",
+ QueryActionType.FILE_ACTION,
+ "Display size of <file> in bytes")
+ {
+ public String action()
+ {
+ return getFileLength(fileFromTeXPath(getRequiredArgument(0)));
+ }
+ },
+ new QueryAction("list", "i", 1, 2, "<sep> <dir> [<sort>]",
+ QueryActionType.FILE_ACTION,
+ String.format("Display list of all files in <dir> separated by <sep>. If <sort> is omitted, the default order is used otherwise <sort> may be one of the following: %s",
+ FileSortType.getFileSortOptions()))
+ {
+ public String action()
+ {
+ return getFileList(getRequiredArgument(0),
+ new File(fromTeXPath(getRequiredArgument(1))),
+ FileSortType.getFileSortType(getOptionalArgument(0)),
+ FileListType.FILE_LIST_ANY);
+ }
+ },
+ new QueryAction("filterlist", "f", 1, 3, "<sep> <regex> <dir> [<sort>]",
+ QueryActionType.FILE_ACTION,
+ String.format("Display list of files in <dir> that fully match <regex> separated by <sep>. If <sort> is omitted, the default order is used otherwise <sort> may be one of the following: %s",
+ FileSortType.getFileSortOptions()))
+ {
+ public String action()
+ {
+ return getFilterFileList(
+ getRequiredArgument(0),
+ getRequiredArgument(1),
+ new File(fromTeXPath(getRequiredArgument(2))),
+ FileSortType.getFileSortType(getOptionalArgument(0)),
+ FileListType.FILE_LIST_ANY);
+ }
+ },
+ new QueryAction("list-dir", "id", 1, 2, "<sep> <dir> [<sort>]",
+ QueryActionType.FILE_ACTION,
+ String.format("Display list of all sub-directories in <dir> separated by <sep>. If <sort> is omitted, the default order is used otherwise <sort> may be one of the following: %s",
+ FileSortType.getFileSortOptions()))
+ {
+ public String action()
+ {
+ return getFileList(getRequiredArgument(0),
+ new File(fromTeXPath(getRequiredArgument(1))),
+ FileSortType.getFileSortType(getOptionalArgument(0)),
+ FileListType.FILE_LIST_DIRECTORIES_ONLY);
+ }
+ },
+ new QueryAction("filterlist-dir", "fd", 1, 3, "<sep> <regex> <dir> [<sort>]",
+ QueryActionType.FILE_ACTION,
+ String.format("Display list of sub-directories in <dir> that fully match <regex> separated by <sep>. If <sort> is omitted, the default order is used otherwise <sort> may be one of the following: %s",
+ FileSortType.getFileSortOptions()))
+ {
+ public String action()
+ {
+ return getFilterFileList(
+ getRequiredArgument(0),
+ getRequiredArgument(1),
+ new File(fromTeXPath(getRequiredArgument(2))),
+ FileSortType.getFileSortType(getOptionalArgument(0)),
+ FileListType.FILE_LIST_DIRECTORIES_ONLY);
+ }
+ },
+ new QueryAction("list-regular", "ir", 1, 2, "<sep> <dir> [<sort>]",
+ QueryActionType.FILE_ACTION,
+ String.format("Display list of all regular files in <dir> separated by <sep>. If <sort> is omitted, the default order is used otherwise <sort> may be one of the following: %s",
+ FileSortType.getFileSortOptions()))
+ {
+ public String action()
+ {
+ return getFileList(getRequiredArgument(0),
+ new File(fromTeXPath(getRequiredArgument(1))),
+ FileSortType.getFileSortType(getOptionalArgument(0)),
+ FileListType.FILE_LIST_REGULAR_FILES_ONLY);
+ }
+ },
+ new QueryAction("filterlist-regular", "fr", 1, 3, "<sep> <regex> <dir> [<sort>]",
+ QueryActionType.FILE_ACTION,
+ String.format("Display list of regular files in <dir> that fully match <regex> separated by <sep>. If <sort> is omitted, the default order is used otherwise <sort> may be one of the following: %s",
+ FileSortType.getFileSortOptions()))
+ {
+ public String action()
+ {
+ return getFilterFileList(
+ getRequiredArgument(0),
+ getRequiredArgument(1),
+ new File(fromTeXPath(getRequiredArgument(2))),
+ FileSortType.getFileSortType(getOptionalArgument(0)),
+ FileListType.FILE_LIST_REGULAR_FILES_ONLY);
+ }
+ },
+ new QueryAction("walk", "w", 1, 3, "<sep> <regex> <dir> [<sort>]",
+ QueryActionType.FILE_ACTION,
+ String.format("Display list of regular non-hidden files in <dir> (descending sub-directories) that fully match <regex> separated by <sep>. The starting directory <dir> may not be outside the current working directory. This action is not available for texosquery-jre5. If <sort> is omitted, the default order is used otherwise <sort> may be one of the following: %s",
+ FileSortType.getFileSortOptions()), 2)
+ {
+ public String action()
+ {
+ return walk(
+ getRequiredArgument(0),
+ getRequiredArgument(1),
+ new File(fromTeXPath(getRequiredArgument(2))),
+ FileSortType.getFileSortType(getOptionalArgument(0)));
+ }
+ },
+ new QueryAction("uri", "u", 0, 1, "<file>",
+ QueryActionType.FILE_ACTION, "Display the URI of <file>")
+ {
+ public String action()
+ {
+ return fileURI(fileFromTeXPath(getRequiredArgument(0)));
+ }
+ },
+ new QueryAction("path", "p", 0, 1, "<file>",
+ QueryActionType.FILE_ACTION, "Display the canonical path of <file>")
+ {
+ public String action()
+ {
+ return filePath(fileFromTeXPath(getRequiredArgument(0)));
+ }
+ },
+ new QueryAction("dirname", "e", 0, 1, "<file>",
+ QueryActionType.FILE_ACTION,
+ "Display the canonical path of the parent of <file>")
+ {
+ public String action()
+ {
+ return parentPath(fileFromTeXPath(getRequiredArgument(0)));
+ }
+ }
+ };
+
+ /**
+ * Application name.
+ */
+ private String name;
+
+ public static final int DEFAULT_COMPATIBLE=2;
+
+ private static final String VERSION_NUMBER = "1.6";
+ private static final String VERSION_DATE = "2017-06-20";
+ private static final char BACKSLASH = '\\';
+ private static final long ZERO = 0L;
+
+ /**
+ * Initialise current date-time for consistency.
+ */
+
+ private Date now = new Date();
+
+ /**
+ * openin_any settings
+ */
+ protected static final char OPENIN_UNSET=0; // unset
+ protected static final char OPENIN_A='a'; // any
+ protected static final char OPENIN_R='r'; // restricted
+ protected static final char OPENIN_P='p'; // paranoid
+
+ private char openin = OPENIN_UNSET;
+
+ private File texmfoutput = null;
+
+ /**
+ * If not null, strip from the start of returned path names.
+ * @since 1.5
+ */
+ private String stripFilePrefix = null;
+
+ /**
+ *Instead of using the above, provide a regular expression and replacement.
+ * @since 1.5
+ */
+ private String pathRegExp=null, pathReplacement=null;
+
+ /**
+ * If not null, strip from the start of returned URI path names
+ * (after file:/).
+ * @since 1.5
+ */
+ private String stripURIPrefix = null;
+
+ /**
+ *Instead of using the above, provide a regular expression and replacement.
+ * @since 1.5
+ */
+ private String uriRegExp=null, uriReplacement=null;
+
+ /**
+ *Charset for stdout (allows user to override default)
+ * @since 1.6
+ */
+ private String fileEncoding=null;
+
+ /**
+ * Debug level. (0 = no debugging, 1 or more print error messages to
+ * STDERR, 2 or more include stack trace, 3 or more include
+ * informational messages.)
+ */
+ private int debugLevel = 0;
+
+ public static final int DEFAULT_DEBUG_LEVEL=3;
+ public static final int DEBUG_ERROR_LEVEL=1;
+ public static final int DEBUG_STACK_TRACE_LEVEL=2;
+ public static final int DEBUG_INFO_LEVEL=3;
+
+ /**
+ * Compatibility mode. Version 1.2 replaces escapeHash with
+ * escapeSpChars, which switches to using \\fhsh etc. Provide a
+ * mode to restore the previous behaviour.
+ */
+ private int compatible = DEFAULT_COMPATIBLE;
+
+ // TeX can only go up to 2147483647, so set the maximum number
+ // of digits provided for the number formatter.
+
+ private static final int MAX_DIGIT_FORMAT=10;
+
+ // Dollar symbol
+ private static final char DOLLAR_CHAR=0x0024;
+
+ // Cent symbol
+ private static final char CENT_CHAR=0x00A2;
+
+ // Pound symbol
+ private static final char POUND_CHAR=0x00A3;
+
+ // Pound symbol as a string
+ private static final String POUND_STRING=""+POUND_CHAR;
+
+ // Currency symbol
+ private static final char CURRENCY_CHAR=0x00A4;
+
+ // Yen symbol
+ private static final char YEN_CHAR=0x00A5;
+
+ // ECU symbol
+ private static final char ECU_CHAR=0x20A0;
+
+ // Colon currency symbol
+ private static final char COLON_CURRENCY_CHAR=0x20A1;
+
+ // Cruzeiro symbol
+ private static final char CRUZEIRO_CHAR=0x20A2;
+
+ // Franc symbol
+ private static final char FRANC_CHAR=0x20A3;
+
+ // Lira symbol
+ private static final char LIRA_CHAR=0x20A4;
+
+ // Mill currency symbol
+ private static final char MILL_CURRENCY_CHAR=0x20A5;
+
+ // Naira symbol
+ private static final char NAIRA_CHAR=0x20A6;
+
+ // Peseta symbol
+ private static final char PESETA_CHAR=0x20A7;
+
+ // Legacy rupee symbol
+ private static final char LEGACY_RUPEE_CHAR=0x20A8;
+
+ // Won symbol
+ private static final char WON_CHAR=0x20A9;
+
+ // New sheqel symbol
+ private static final char NEW_SHEQEL_CHAR=0x20AA;
+
+ // Dong symbol
+ private static final char DONG_CHAR=0x20AB;
+
+ // Euro symbol
+ private static final char EURO_CHAR=0x20AC;
+
+ // Kip symbol
+ private static final char KIP_CHAR=0x20AD;
+
+ // Tugrik symbol
+ private static final char TUGRIK_CHAR=0x20AE;
+
+ // Drachma symbol
+ private static final char DRACHMA_CHAR=0x20AF;
+
+ // German penny symbol
+ private static final char GERMAN_PENNY_CHAR=0x20B0;
+
+ // Peso symbol
+ private static final char PESO_CHAR=0x20B1;
+
+ // Guarani symbol
+ private static final char GUARANI_CHAR=0x20B2;
+
+ // Austral symbol
+ private static final char AUSTRAL_CHAR=0x20B3;
+
+ // Hryvnia symbol
+ private static final char HRYVNIA_CHAR=0x20B4;
+
+ // Cedi symbol
+ private static final char CEDI_CHAR=0x20B5;
+
+ // Livre tournois symbol
+ private static final char LIVRE_TOURNOIS_CHAR=0x20B6;
+
+ // Spesmilo symbol
+ private static final char SPESMILO_CHAR=0x20B7;
+
+ // Tenge symbol
+ private static final char TENGE_CHAR=0x20B8;
+
+ // Official rupee symbol
+ private static final char RUPEE_CHAR=0x20B9;
+
+ // Turkish lira symbol
+ private static final char TURKISH_LIRA_CHAR=0x20BA;
+
+ // Nordic mark symbol
+ private static final char NORDIC_MARK_CHAR=0x20BB;
+
+ // Manat symbol
+ private static final char MANAT_CHAR=0x20BC;
+
+ // Ruble symbol
+ private static final char RUBLE_CHAR=0x20BD;
+
+ // Per mille symbol
+ private static final char PERMILLE_CHAR=0x2030;
+}
diff --git a/support/texosquery/java/TeXOSQueryJRE5.java b/support/texosquery/java/TeXOSQueryJRE5.java
new file mode 100644
index 0000000000..15c6bd6c50
--- /dev/null
+++ b/support/texosquery/java/TeXOSQueryJRE5.java
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+import java.io.BufferedReader;
+import java.util.Locale;
+import java.util.Calendar;
+import java.text.DecimalFormatSymbols;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * Main class. Supports Java 5 onwards.
+ * @author Nicola Talbot
+ * @version 1.2
+ * @since 1.2
+ */
+public class TeXOSQueryJRE5 extends TeXOSQuery
+{
+ public TeXOSQueryJRE5()
+ {
+ super("texosquery-jre5");
+ }
+
+ /**
+ * Main method.
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ try
+ {
+ (new TeXOSQueryJRE5()).processArgs(args);
+ }
+ catch (IllegalArgumentException e)
+ {
+ System.err.println(e.getMessage());
+ System.exit(1);
+ }
+ }
+}
diff --git a/support/texosquery/java/TeXOSQueryJRE7.java b/support/texosquery/java/TeXOSQueryJRE7.java
new file mode 100644
index 0000000000..0abb09d0aa
--- /dev/null
+++ b/support/texosquery/java/TeXOSQueryJRE7.java
@@ -0,0 +1,197 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+import java.util.Locale;
+import java.util.Locale.Builder;
+import java.util.Calendar;
+import java.io.Serializable;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Main class. Supports Java 7 onwards.
+ * @author Nicola Talbot
+ * @version 1.2
+ * @since 1.2
+ */
+public class TeXOSQueryJRE7 extends TeXOSQuery
+{
+ public TeXOSQueryJRE7()
+ {
+ super("texosquery");
+ }
+
+ /**
+ * Converts the given directory to a canonical path
+ * and checks to make sure it has a parent directory.
+ * @param dir the directory to check
+ * @return the canonical path
+ * @throws IOException if directory can't be converted to a
+ * canonical path or if the canonical path doesn't have a parent
+ * directory or is outside the current working directory
+ */
+ @Override
+ protected File checkDirectoryListing(File dir) throws IOException
+ {
+ dir = dir.getCanonicalFile();
+
+ if (dir.getParentFile() == null)
+ {
+ throw new IOException(String.format(
+ "Listing on root directory not permitted: %s", dir));
+ }
+
+ File cwd = new File(getSystemProperty("user.dir", "."));
+
+ if (!cwd.equals(dir) && !isFileInTree(dir, cwd))
+ {
+ throw new IOException(String.format(
+ "Listing outside cwd path not permitted: %s", dir));
+ }
+
+ return dir;
+ }
+
+ /**
+ * Recursive file listing. This method must have the CWD or a
+ * descendent as the starting directory. It will return list of
+ * files relative to the starting directory where the basename
+ * matches the supplied regular expression. Hidden files/directories
+ * and symbolic links are skipped regardless of the openin_any setting.
+ * Files without read access are also omitted from the list.
+ *
+ * @param separator separator to use in returned list
+ * @param regex regular expression used to match file basenames
+ * @param directory starting directory (must be cwd or a
+ * descendent of cwd)
+ * @return list of relative paths
+ */
+ @Override
+ public String walk(String separator,
+ String regex, File directory,
+ FileSortType sortType)
+ {
+ try
+ {
+ return FileWalkVisitor.walk(this, separator,
+ regex, directory, sortType);
+ }
+ catch (Exception e)
+ {
+ debug(String.format("Can't walk directory: %s",
+ directory.toString()), e);
+ }
+
+ return "";
+ }
+
+ /**
+ * Gets the script for the given locale.
+ * @param locale The locale
+ * @return The language script associated with the given locale or null if not available
+ */
+ @Override
+ public String getScript(Locale locale)
+ {
+ return locale.getScript();
+ }
+
+ /**
+ * Gets the locale from the given language tag.
+ * @param languageTag The language tag
+ * @return The locale that closest matches the language tag
+ */
+ @Override
+ public Locale getLocale(String languageTag)
+ {
+ Locale locale = Locale.forLanguageTag(languageTag);
+
+ // Locale.forLanguageTag() doesn't recognise
+ // numeric regions. So test for a numeric region.
+
+ String region = locale.getCountry();
+
+ try
+ {
+ region = getRegionAlpha2Code(Integer.parseInt(region));
+ }
+ catch (NumberFormatException e)
+ {
+ // region isn't numeric, so we don't need to do anything
+ // else
+ return locale;
+ }
+
+ Locale.Builder builder = new Locale.Builder();
+ builder.setLocale(locale);
+ builder.setRegion(region);
+
+ return builder.build();
+ }
+
+ /**
+ * Gets the language tag for the given locale.
+ * @param locale The locale or null for the default locale
+ * @return The language tag
+ */
+ @Override
+ public String getLanguageTag(Locale locale)
+ {
+ if (locale == null)
+ {
+ locale = Locale.getDefault();
+ }
+
+ return locale.toLanguageTag();
+ }
+
+ /**
+ * Gets the week year for the given calendar.
+ * @return The week year
+ */
+ @Override
+ public int getWeekYear(Calendar cal)
+ {
+ try
+ {
+ return cal.isWeekDateSupported() ?
+ cal.getWeekYear() : cal.get(Calendar.YEAR);
+ }
+ catch (UnsupportedOperationException e)
+ {
+ // shouldn't happen with the above conditional, but just in
+ // case...
+
+ debug(e.getMessage(), e);
+ return cal.get(Calendar.YEAR);
+ }
+ }
+
+ /**
+ * Main method.
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ try
+ {
+ (new TeXOSQueryJRE7()).processArgs(args);
+ }
+ catch (IllegalArgumentException e)
+ {
+ System.err.println(e.getMessage());
+ System.exit(1);
+ }
+ }
+}
diff --git a/support/texosquery/java/TeXOSQueryJRE8.java b/support/texosquery/java/TeXOSQueryJRE8.java
new file mode 100644
index 0000000000..5884fb9d63
--- /dev/null
+++ b/support/texosquery/java/TeXOSQueryJRE8.java
@@ -0,0 +1,384 @@
+/*
+ Copyright (C) 2016 Nicola L.C. Talbot
+ www.dickimaw-books.com
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license or (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+*/
+package com.dickimawbooks.texosquery;
+
+import java.util.Locale;
+import java.util.Locale.Builder;
+import java.util.Calendar;
+import java.util.Map;
+import java.util.Arrays;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Main class. Supports Java 8 onwards.
+ * @author Nicola Talbot
+ * @version 1.2
+ * @since 1.2
+ */
+public class TeXOSQueryJRE8 extends TeXOSQuery
+{
+ public TeXOSQueryJRE8()
+ {
+ super("texosquery-jre8");
+ }
+
+ /*
+ * The fallback for openin_any is now the same for
+ * all variants to allow texosquery-jre8 to work with MiKTeX
+ * so openinFallbackValue method has been removed from this
+ * subclass in v1.3.1.
+ */
+
+ /**
+ * Converts the given directory to a canonical path
+ * and checks to make sure it has a parent directory.
+ * @param dir the directory to check
+ * @return the canonical path
+ * @throws IOException if directory can't be converted to a
+ * canonical path or if the canonical path doesn't have a parent
+ * directory or is outside the current working directory
+ */
+ @Override
+ protected File checkDirectoryListing(File dir) throws IOException
+ {
+ dir = dir.getCanonicalFile();
+
+ if (dir.getParentFile() == null)
+ {
+ throw new IOException(String.format(
+ "Listing on root directory not permitted: %s", dir));
+ }
+
+ File cwd = new File(getSystemProperty("user.dir", "."));
+
+ if (!cwd.equals(dir) && !isFileInTree(dir, cwd))
+ {
+ throw new IOException(String.format(
+ "Listing outside cwd path not permitted: %s", dir));
+ }
+
+ return dir;
+ }
+
+ /**
+ * Recursive file listing. This method must have the CWD or a
+ * descendent as the starting directory. It will return list of
+ * files relative to the starting directory where the basename
+ * matches the supplied regular expression. Hidden files/directories
+ * and symbolic links are skipped regardless of the openin_any setting.
+ * Files without read access are also omitted from the list.
+ *
+ * @param separator separator to use in returned list
+ * @param regex regular expression used to match file basenames
+ * @param directory starting directory (must be cwd or a
+ * descendent of cwd)
+ * @return list of relative paths
+ */
+ @Override
+ public String walk(String separator,
+ String regex, File directory,
+ FileSortType sortType)
+ {
+ try
+ {
+ return FileWalkVisitor.walk(this, separator,
+ regex, directory, sortType);
+ }
+ catch (Exception e)
+ {
+ debug(String.format("Can't walk directory: %s",
+ directory.toString()), e);
+ }
+
+ return "";
+ }
+
+
+ /**
+ * Gets the script for the given locale.
+ * @param locale The locale
+ * @return The language script associated with the given locale or null if not available
+ */
+ @Override
+ public String getScript(Locale locale)
+ {
+ return locale.getScript();
+ }
+
+ /**
+ * Gets the locale from the given language tag.
+ * @param languageTag The language tag
+ * @return The locale that closest matches the language tag
+ */
+ @Override
+ public Locale getLocale(String languageTag)
+ {
+ Locale locale = Locale.forLanguageTag(languageTag);
+
+ // Locale.forLanguageTag() doesn't recognise
+ // numeric regions. So test for a numeric region.
+
+ String region = locale.getCountry();
+
+ try
+ {
+ region = getRegionAlpha2Code(Integer.parseInt(region));
+ }
+ catch (NumberFormatException e)
+ {
+ // region isn't numeric, so we don't need to do anything
+ // else
+ return locale;
+ }
+
+ Locale.Builder builder = new Locale.Builder();
+ builder.setLocale(locale);
+ builder.setRegion(region);
+
+ return builder.build();
+ }
+
+ /**
+ * Gets the language tag for the given locale.
+ * @param locale The locale or null for the default locale
+ * @return The language tag
+ */
+ @Override
+ public String getLanguageTag(Locale locale)
+ {
+ if (locale == null)
+ {
+ locale = Locale.getDefault();
+ }
+
+ return locale.toLanguageTag();
+ }
+
+ /**
+ * Gets the week year for the given calendar.
+ * @return The week year
+ */
+ @Override
+ public int getWeekYear(Calendar cal)
+ {
+ try
+ {
+ return cal.isWeekDateSupported() ?
+ cal.getWeekYear() : cal.get(Calendar.YEAR);
+ }
+ catch (UnsupportedOperationException e)
+ {
+ // shouldn't happen with the above conditional, but just in
+ // case...
+
+ debug(e.getMessage(), e);
+ return cal.get(Calendar.YEAR);
+ }
+ }
+
+ /** Gets the standalone month names for the locale data.
+ * These are only available for Java 8, so just return the
+ * month names used in the date format instead.
+ * @param cal The calendar
+ * @param locale The locale
+ * @return month names
+ */
+ public String getStandaloneMonths(Calendar cal, Locale locale)
+ {
+ Map<String,Integer> map = cal.getDisplayNames(Calendar.MONTH,
+ Calendar.LONG_STANDALONE, locale);
+
+ // Is the map order? Not sure so save in an array
+
+ String[] names = new String[12];
+
+ for (Map.Entry<String,Integer> entry : map.entrySet())
+ {
+ int idx = entry.getValue().intValue();
+
+ if (idx < 12)
+ {// Java has a 13th month that we're ignoring
+ names[idx] = entry.getKey();
+ }
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0; i < names.length; i++)
+ {
+ builder.append(String.format("{%s}", escapeText(names[i])));
+ }
+
+ return builder.toString();
+ }
+
+ /** Gets the standalone short month names for the locale data.
+ * @param cal The calendar
+ * @param locale The locale
+ * @return month names
+ */
+ public String getStandaloneShortMonths(Calendar cal, Locale locale)
+ {
+ Map<String,Integer> map = cal.getDisplayNames(Calendar.MONTH,
+ Calendar.SHORT_STANDALONE, locale);
+
+ // Is the map order? Not sure so save in an array
+
+ String[] names = new String[12];
+
+ for (Map.Entry<String,Integer> entry : map.entrySet())
+ {
+ int idx = entry.getValue().intValue();
+
+ if (idx < names.length)
+ {
+ names[idx] = entry.getKey();
+ }
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0; i < names.length; i++)
+ {
+ builder.append(String.format("{%s}", escapeText(names[i])));
+ }
+
+ return builder.toString();
+ }
+
+
+ /** Gets the standalone week day names for the locale data.
+ * @param cal The calendar
+ * @param locale The locale
+ * @return week day names
+ */
+ public String getStandaloneWeekdays(Calendar cal, Locale locale)
+ {
+ Map<String,Integer> map = cal.getDisplayNames(Calendar.DAY_OF_WEEK,
+ Calendar.LONG_STANDALONE, locale);
+
+ String[] names = new String[7];
+
+ for (Map.Entry<String,Integer> entry : map.entrySet())
+ {
+ switch (entry.getValue().intValue())
+ {
+ case Calendar.MONDAY:
+ names[0] = entry.getKey();
+ break;
+ case Calendar.TUESDAY:
+ names[1] = entry.getKey();
+ break;
+ case Calendar.WEDNESDAY:
+ names[2] = entry.getKey();
+ break;
+ case Calendar.THURSDAY:
+ names[3] = entry.getKey();
+ break;
+ case Calendar.FRIDAY:
+ names[4] = entry.getKey();
+ break;
+ case Calendar.SATURDAY:
+ names[5] = entry.getKey();
+ break;
+ case Calendar.SUNDAY:
+ names[6] = entry.getKey();
+ break;
+ }
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0; i < names.length; i++)
+ {
+ builder.append(String.format("{%s}", escapeText(names[i])));
+ }
+
+ return builder.toString();
+ }
+
+ /** Gets the standalone short week day names for the locale data.
+ * @param cal The calendar
+ * @param locale The locale
+ * @return week day names
+ */
+ public String getStandaloneShortWeekdays(Calendar cal, Locale locale)
+ {
+ Map<String,Integer> map = cal.getDisplayNames(Calendar.DAY_OF_WEEK,
+ Calendar.SHORT_STANDALONE, locale);
+
+ String[] names = new String[7];
+
+ for (Map.Entry<String,Integer> entry : map.entrySet())
+ {
+ switch (entry.getValue().intValue())
+ {
+ case Calendar.MONDAY:
+ names[0] = entry.getKey();
+ break;
+ case Calendar.TUESDAY:
+ names[1] = entry.getKey();
+ break;
+ case Calendar.WEDNESDAY:
+ names[2] = entry.getKey();
+ break;
+ case Calendar.THURSDAY:
+ names[3] = entry.getKey();
+ break;
+ case Calendar.FRIDAY:
+ names[4] = entry.getKey();
+ break;
+ case Calendar.SATURDAY:
+ names[5] = entry.getKey();
+ break;
+ case Calendar.SUNDAY:
+ names[6] = entry.getKey();
+ break;
+ }
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0; i < names.length; i++)
+ {
+ builder.append(String.format("{%s}", escapeText(names[i])));
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ public void sortFileList(String[] list, File directory,
+ FileSortType sortType)
+ {
+ Arrays.parallelSort(list, new FileSortComparator(directory, sortType));
+ }
+
+ /**
+ * Main method.
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ try
+ {
+ (new TeXOSQueryJRE8()).processArgs(args);
+ }
+ catch (IllegalArgumentException e)
+ {
+ System.err.println(e.getMessage());
+ System.exit(1);
+ }
+ }
+}
diff --git a/support/texosquery/texosquery.dtx b/support/texosquery/texosquery.dtx
new file mode 100644
index 0000000000..e35098d78d
--- /dev/null
+++ b/support/texosquery/texosquery.dtx
@@ -0,0 +1,8136 @@
+%\iffalse
+%<*package>
+%% \CharacterTable
+%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
+%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
+%% Digits \0\1\2\3\4\5\6\7\8\9
+%% Exclamation \! Double quote \" Hash (number) \#
+%% Dollar \$ Percent \% Ampersand \&
+%% Acute accent \' Left paren \( Right paren \)
+%% Asterisk \* Plus \+ Comma \,
+%% Minus \- Point \. Solidus \/
+%% Colon \: Semicolon \; Less than \<
+%% Equals \= Greater than \> Question mark \?
+%% Commercial at \@ Left bracket \[ Backslash \\
+%% Right bracket \] Circumflex \^ Underscore \_
+%% Grave accent \` Left brace \{ Vertical bar \|
+%% Right brace \} Tilde \~}
+%</package>
+%\fi
+% \iffalse
+% Doc-Source file
+% Copyright (C) 2017 Nicola Talbot, all rights reserved.
+% \fi
+% \iffalse
+%<*driver>
+\documentclass{ltxdoc}
+
+\usepackage[T1]{fontenc}
+\usepackage[utf8]{inputenc}
+\usepackage{etoolbox}
+\usepackage{textcomp}
+\usepackage{tgtermes}
+\usepackage{upquote}
+\usepackage{metalogo}
+\usepackage[colorlinks,hyperindex=false]{hyperref}
+\usepackage[abbreviations,nomain,nonumberlist,nogroupskip]{glossaries-extra}
+
+\makeglossaries
+
+\makeatletter% doc.sty interferes with upquote.sty
+\def\@verbatim{\trivlist \item[]\if@minipage\else\vskip\parskip\fi
+ \leftskip\@totalleftmargin\rightskip\z@
+ \parindent\z@\parfillskip\@flushglue\parskip\z@
+ \@@par
+ \@tempswafalse
+ \def\par{\if@tempswa\hbox{}\fi\@tempswatrue\@@par
+ \penalty\interlinepenalty
+ \check@percent}%
+ \obeylines
+ \@noligs
+ \let\do\@makeother \dospecials}
+
+\renewcommand{\meta@font@select}{\rmfamily\itshape}
+\makeatother
+
+\CheckSum{3469}
+
+\RecordChanges
+\PageIndex
+\CodelineNumbered
+\IndexPrologue{\clearpage\phantomsection\section*{Index}\markboth{Index}{Index}%
+\addcontentsline{toc}{section}{Index}}
+\setcounter{IndexColumns}{2}
+
+\renewcommand*{\usage}[1]{\hyperpage{#1}}
+\renewcommand*{\main}[1]{\hyperpage{#1}}
+
+\newcommand*{\ics}[1]{\cs{#1}\SpecialMainIndex{#1}}
+
+\newcommand*{\qt}[1]{``#1''}
+\newcommand*{\sty}[1]{\textsf{#1}}
+\newcommand*{\app}[1]{\texttt{#1}}
+\newcommand*{\file}[1]{\texttt{#1}}
+\providecommand*{\eTeX}{e\TeX}
+
+\newcommand*{\shortargfmt}[1]{\texttt{-#1}}
+\newcommand*{\longargfmt}[1]{\texttt{-{}-#1}}
+
+\newcommand*{\shortarg}[1]{\shortargfmt{#1}%
+ \index{texosquery options=\app{texosquery} options>#1=\protect\shortargfmt{-#1}|hyperpage}}
+\newcommand*{\longarg}[1]{\longargfmt{#1}%
+ \index{texosquery options=\app{texosquery} options>#1=\protect\longargfmt{#1}|hyperpage}}
+
+\newcommand*{\Index}[1]{#1\index{#1|hyperpage}}
+
+\newenvironment{important}
+ {\begin{quote}\textbf{Important Note:}}
+ {\end{quote}}
+
+\newenvironment{definition}
+ {\begin{flushleft}\ttfamily}
+ {\end{flushleft}}
+
+\setcounter{secnumdepth}{2}
+
+\pdfstringdefDisableCommands{\renewcommand\meta[1]{#1}}
+
+\glssetcategoryattribute{abbreviation}{nohyperfirst}{true}
+
+\setabbreviationstyle[noexpand]{short}
+\newabbreviation[category=noexpand]{IETF}{IETF}{Internet Engineering Task Force}
+\newabbreviation[category=noexpand]{BCP}{BCP}{Best Common Practice}
+\newabbreviation[category=noexpand]{POSIX}{POSIX}{Portable Operating
+System Interface}
+\newabbreviation[category=noexpand]{CTAN}{CTAN}{Comprehensive \TeX\
+Archive Network}
+\newabbreviation[category=noexpand]{UTF}{UTF}{Unicode Transformation
+Format}
+\newabbreviation[category=noexpand]{ASCII}{ASCII}{American Standard
+Code for Information Interchange}
+\newabbreviation[category=noexpand]{ISO}{ISO}{International
+Organization for Standardization}
+
+\newabbreviation{OS}{OS}{operating system}
+\newabbreviation{JRE}{JRE}{Java Runtime Environment}
+\newabbreviation{CLDR}{CLDR}{Unicode Consortium's Common Locale Data Repository}
+
+\begin{document}
+\DocInput{texosquery.dtx}
+\end{document}
+%</driver>
+%\fi
+%
+%\DeleteShortVerb{|}
+%
+%\title{texosquery: query OS information from \TeX}
+%\author{Nicola L. C. Talbot\\
+%\href{http://www.dickimaw-books.com/}{\nolinkurl{dickimaw-books.com}}
+%\and Paulo Cereda}
+%\date{2017-06-20 (v1.6)}
+%\maketitle
+%
+%\begin{abstract}
+%The \sty{texosquery} bundle provides the \app{texosquery.jar} application (and
+%variations \app{texosquery-jre8.jar} and \app{texosquery-jre5.jar})
+%This is a cross-platform Java application to query certain
+%\gls{OS} and locale information. The application is
+%specifically designed for use within \TeX's shell escape mechanism,
+%through the \cs{TeXOSQuery} command provided by the \sty{texosquery} package
+%(\file{texosquery.tex} and \file{texosquery.sty}).
+%
+%The \cs{TeXOSQuery} command performs more than a simple piped input as it
+%first changes category codes of various problematic characters and locally
+%defines some short control sequences that are used in the application's result.
+%These commands aren't defined outside of \cs{TeXOSQuery}, so a
+%direct piped input may cause undefined control sequences. If you
+%really want to use this direct method rather than using
+%\cs{TeXOSQuery}, then you will need to run \app{texosquery} in
+%backward compatibility mode~0 or~1 (using \texttt{\longarg{compatible} 1}).
+%The first two versions of \app{texosquery} didn't use those short
+%commands.
+%\end{abstract}
+%
+%\begin{important}
+%You will need \TeX's piped shell escape enabled
+%and you will also need the \gls{JRE} installed.
+%MiKTeX users will need to add \verb|--enable-pipes| to TeX's
+%command line options. (Alternatively, use \cs{TeXOSQueryFromFile}
+%if you can't use the piped shell escape.)
+%The configuration file \hyperref[itm:cfg]{\file{texosquery.cfg}}
+%should be edited to reflect your system set-up before use.
+%\end{important}
+%
+%There are three variations of the \app{texosquery} application provided:
+%\begin{itemize}
+%\item \file{texosquery.jar}: requires at least Java~7, has medium locale
+%support, obeys \texttt{openin\_any} but has additional
+%restrictions imposed for security reasons (no listings outside the
+%current working directory path); Note that Java 7 has reached its
+%\href{http://www.oracle.com/technetwork/java/eol-135779.html}{end
+%of life and is now deprecated}.
+%\item \file{texosquery-jre8.jar}: requires at least Java~8, has best locale
+%support, obeys \texttt{openin\_any} but has additional
+%restrictions imposed for security reasons (no listings outside the
+%current working directory path);
+%\item \file{texosquery-jre5.jar}: requires at least Java~5, has poor locale
+%support (language scripts not recognised), doesn't have the walk
+%action, obeys \texttt{openin\_any} but doesn't have the extra restrictions of the
+%Java 7 and 8 versions for the listing functions. Note that Java~5 and~6
+%are deprecated. Old deprecated versions are considered a security
+%risk.
+%\end{itemize}
+%The default is \file{texosquery.jar}. Throughout this document
+%\app{texosquery} is used to reference the application, regardless
+%which of these three jar files you've chosen to use. See
+%section~\ref{sec:setup} for further details.
+%
+%The aim of the original version of \app{texosquery} was to provide
+%a way of accessing the operating system's locale information.
+%Version 1.3 of the \sty{tracklang} package provides
+%\cs{TrackLangQueryEnv} which uses \app{kpsewhich} to query the
+%appropriate locale environment variable (such as \verb|$LANG|
+%or \verb|LC_ALL|). Unfortunately this doesn't work under Windows as
+%the locale information there is stored in the registry. The Lua
+%\texttt{os.setlocale(nil)} function can simply return \texttt{C} or
+%\texttt{POSIX}, which isn't helpful from \sty{tracklang}'s point of
+%view. Although Java has its drawbacks, it's one of the most
+%ubiquitous platform-independent methods to obtain this information.
+%Since it seemed overkill to write a Java application that simply
+%returned the locale, I decided to add a few extra functions that
+%might be of use, but accessing locale information was, and still
+%is, the primary purpose of this application.
+%
+%Although the \gls{POSIX} environment variables, such as
+%\verb|$LC_ALL|, are easy to read with \app{kpsewhich}, these days
+%the \gls{IETF} \gls{BCP}~47 language tag is the more appropriate way
+%of identifying a locale, so version 1.2 has added the \longarg{bcp47}
+%function to support this. The \sty{tracklang} package has similarly added
+%\cs{TrackLanguageTag}\marg{IETF tag}.
+%
+%The \sty{locale} package occasionally referenced in this document
+%is still under development at the time of writing. The
+%\longarg{numeric}, \longarg{locale-data}, \longarg{date-time} and
+%\longarg{time-zones} options are designed to
+%interface with the \sty{locale} package, so although
+%\sty{texosquery} and \sty{locale} will be distributed separately,
+%version 1.2 of \app{texosquery} is being
+%developed alongside version 1.0 of the \sty{locale} package.
+%The aim of the \sty{locale} package is to use both \sty{tracklang}
+%and \sty{texosquery} to automatically set up the document language.
+%For example, in the following \LaTeX\ document
+%\begin{verbatim}
+%\documentclass{article}
+%\usepackage{locale}
+%\begin{document}
+%Language: \CurrentLocaleLanguageNativeName.
+%Region: \CurrentLocaleRegionNativeName.
+%Today: \CurrentLocaleDate. (Compare with \today.)
+%Time: \CurrentLocaleTime.
+%Currency Symbol: \CurrentLocaleCurrency
+%Integer:
+%\texosqueryfmtnumber{\CurrentLocaleIntegerPattern}{123456}{0}{0}
+%Decimal:
+%\texosqueryfmtnumber{\CurrentLocaleDecimalPattern}{123456}{78}{0}
+%Percentage:
+%\texosqueryfmtnumber{\CurrentLocalePercentPattern}{0}{65}{0}
+%Currency:
+%\texosqueryfmtnumber{\CurrentLocaleCurrencyPattern}{1234567}{0}{0}
+%\end{document}
+%\end{verbatim}
+%the \sty{locale} package will automatically:
+%\begin{itemize}
+%\item load the \sty{textcomp} package for currency symbols (package
+%option \texttt{symbols=fontawesome} will use \sty{fontawesome}
+%instead);
+%\item if \XeLaTeX\ or \LuaLaTeX:
+% \begin{itemize}
+% \item load \sty{fontspec} (unless option \texttt{fontspec=false}
+% is used);
+% \item load \sty{polyglossia} and use \cs{setmainlanguage}
+% with options that can be determined from the language tag
+% (use package option \texttt{support=babel} to use \sty{babel}
+% regardless of the \LaTeX\ format);
+% \end{itemize}
+% otherwise:
+% \begin{itemize}
+% \item load \sty{inputenc} (default file encoding obtained from
+% \app{texosquery}'s \longarg{codeset-lcs} action);
+% \item load \sty{fontenc} (font encoding obtained using
+% \sty{tracklang} to query the language script);
+% \item load \sty{babel} with the appropriate language label (use
+% \texttt{support=none} to prevent this);
+% \end{itemize}
+%\item load \sty{datetime2} with the \texttt{useregional=text}
+%option (use \texttt{datetime2=false} to prevent this).
+%\end{itemize}
+%
+%The generic \texttt{locale.tex} code doesn't load the above packages, but can
+%still obtain information about the locale:
+%\begin{verbatim}
+%\input locale
+
+%Language: \CurrentLocaleLanguageNativeName.
+%Region: \CurrentLocaleRegionNativeName.
+%Today: \CurrentLocaleDate. (Compare with \today.)
+%Time: \CurrentLocaleTime.
+%Currency Symbol: \CurrentLocaleCurrency
+%Integer:
+%\texosqueryfmtnumber{\CurrentLocaleIntegerPattern}{123456}{0}{0}
+%Decimal:
+%\texosqueryfmtnumber{\CurrentLocaleDecimalPattern}{123456}{78}{0}
+%Percentage:
+%\texosqueryfmtnumber{\CurrentLocalePercentPattern}{0}{65}{0}
+%Currency:
+%\texosqueryfmtnumber{\CurrentLocaleCurrencyPattern}{1234567}{0}{0}
+%\bye
+%\end{verbatim}
+%
+%So that's the reasoning behind the new v1.2 actions. Hopefully the
+%new \sty{locale} package will be uploaded to \gls{CTAN} shortly after the
+%new version of \sty{texosquery}.
+%
+%\clearpage
+%\tableofcontents
+%\clearpage
+%
+%\section{texosquery.jar: the Java application}
+%The \app{texosquery} Java command line application looks up
+%certain system information that may be of use in \TeX\
+%documents. This information can be obtained using
+%native commands, but the Java application allows an
+%\gls{OS}-independent approach with results that can easily be
+%captured by \TeX's shell-escape without having to strip
+%formatting information. It also uses control sequence markup to indicate
+%whether characters should be interpreted literally (such as in file names) or
+%if they should obey their current category code (such as
+%punctuation occurring in textual information) or if they should be
+%interpreted in some other way (such as pattern markup). This markup
+%is expanded by \cs{TeXOSQuery} when it performs the piped shell
+%escape.
+%
+%\begin{important}
+%\app{texosquery} provides read-only actions, and I don't
+%intend adding any actions that modify system settings or files.
+%\end{important}
+%
+%Since the application is designed to work with \TeX\ (through
+%\cs{TeXOSQuery} defined in \file{texosquery.tex}) each action (indicated
+%by a command line switch) will display the result on a single line.
+%For multiple results, each line is grouped. A blank line (or empty group)
+%will be displayed if the information isn't available or is prohibited.
+%A forward slash (\cs{fslh}) is always used as a directory divider,
+%regardless of the operating system, so the result can be used, for example,
+%in \cs{input} or \cs{includegraphics}.
+%
+%For example, I have a 64-bit Linux operating system installed on my
+%computer, so I could use \app{uname} in a bash terminal:
+%\begin{verbatim}
+%uname -o -r
+%\end{verbatim}
+%which (for me) produces:
+%\begin{verbatim}
+%4.1.13-100.fc21.x86_64 GNU/Linux
+%\end{verbatim}
+%I could also run \app{texosquery} directly from the bash terminal:
+%\begin{verbatim}
+%texosquery -o -r
+%\end{verbatim}
+%which produces the rather more cryptic:
+%\begin{verbatim}
+%{Linux}
+%{4\fdot 1\fdot 13\fhyn 100\fdot fc21\fdot x86\fusc 64}
+%\end{verbatim}
+%However \app{texosquery} isn't intended for this direct use. It's
+%intended for use with \cs{TeXOSQuery} provided by
+%\file{texosquery.tex}. Here's a plain \TeX\ document:
+%\begin{verbatim}
+%\input texosquery
+%\TeXOSQuery{\result}{-o -r}
+%\def\parseresult#1#2{OS Name: {\tt #1}. OS Version: {\tt #2}.}
+%\ifx\result\empty
+% Query failed!
+%\else
+% \expandafter\parseresult\result
+%\fi
+%\bye
+%\end{verbatim}
+%The markup commands, such as \cs{fusc}, are now converted to
+%literal characters with category code 12 (\qt{other}), so the
+%underscore isn't a problem. This document is now also platform independent
+%(as long as \app{texosquery} and a recent version of the \gls{JRE} are
+%installed). Unlike \app{uname}, \app{texosquery} also obeys the order of the
+%command line switches, which makes it easier to define the helper
+%command (\cs{parseresult} in the above) that processes the result.
+%
+%\subsection{Installation and Setup}
+%\label{sec:setup}
+%
+%Installation is best done through your \TeX\ package manager.
+%However if for some reason you need to install this package
+%manually the instructions are below. If you install through your
+%package manager, Windows users will probably find that the
+%\file{.jar} files have been converted to \file{.exe} (with the
+%\file{.bat} files omitted) and Unix-like users may find that the
+%bash scripts are missing the \file{.sh} extension (these are actually
+%symbolic links to the distributed \file{.sh} files). See
+%section~\ref{sec:setuptest} to test that the package has been
+%successfully installed.
+%
+%Even if you use your \TeX\ distribution's package manager to
+%install this package, you may still need to edit the
+%\file{texosquery.cfg} file (see step~\ref{itm:cfg} below). It's
+%best to copy this file to your \meta{TEXMFHOME} or \meta{TEXMFLOCAL}
+%tree to avoid losing your changes when the package is updated.
+
+%You can find the correct value of \meta{TEXMFHOME} using
+%\begin{verbatim}
+%kpsewhich -var-value=TEXMFHOME
+%\end{verbatim}
+%Similarly for \meta{TEXMFLOCAL}.
+%
+%You can find where the package manager has put \file{texosquery.cfg} using
+%\begin{verbatim}
+%kpsewhich texosquery.cfg
+%\end{verbatim}
+%
+%This bundle contains the following files:
+%\begin{itemize}
+%\item \file{texosquery.dtx}
+%
+%The DTX file contains the source code for this
+%document, and also the files:
+%\begin{itemize}
+%\item\file{texosquery.tex} (generic \TeX\ code)
+%\item\file{texosquery.sty} (\LaTeX\ package wrapper)
+%\item\file{texosquery.cfg} (configuration file)
+%\end{itemize}
+%The bash scripts (which will need the extensions removed):
+%\begin{itemize}
+%\item\file{texosquery-jre8.sh}
+%\item\file{texosquery.sh}
+%\item\file{texosquery-jre5.sh}
+%\end{itemize}
+%Windows batch files (which will need the extensions changed to
+%\file{.bat})
+%\begin{itemize}
+%\item\file{texosquery-jre8.batch}
+%\item\file{texosquery.batch}
+%\item\file{texosquery-jre5.batch}
+%\end{itemize}
+%
+%\item \file{texosquery.ins} The driver file used to extract all the
+%above files contained in \file{texosquery.dtx}.
+%
+%\item The three different versions of the \app{texosquery}
+%application: \file{texosquery-jre8.jar}, \file{texosquery.jar} and
+%\file{texosquery-jre5.jar}. The source code for these is contained in
+%the \file{java} sub-directory.
+%
+%\item \file{texosquery.pdf} This PDF document.
+%\item \file{README.md} The README file in markdown format.
+%\item \file{CHANGES} Lists major changes for each version.
+%\end{itemize}
+%
+%To install manually (\meta{TEXMF} indicates the TEXMF directory):
+%\begin{enumerate}
+%\item Run
+%\begin{verbatim}
+%tex texosquery.ins
+%\end{verbatim}
+%to extract the \file{.tex}, \file{.sty}, \file{.cfg}, \file{.sh}
+%and \file{.batch} files.
+%
+%\begin{description}
+%\item[Windows] Change the extension of the \file{.batch} files to
+%\file{.bat} (\TeX\ on Windows prohibits the creation of \file{.bat}
+%files). Move the \file{.bat} files to somewhere on
+%your system's path. (You may omit the \file{.bat} files you don't
+%need.) The \file{.sh} files may be deleted.
+%
+%\item[Unix-like] Make the \file{.sh} files executable:
+%\begin{verbatim}
+%chmod u+x texosquery*.sh
+%\end{verbatim}
+%Move the \file{.sh} files to somewhere on your path \emph{without} the
+%\file{.sh} extension. (If the \file{.sh} extension is retained, you
+%will have to edit the \file{texosquery.cfg} file to include it.)
+%For example (if \verb|~/bin| is included in \verb|$PATH|):
+%\begin{verbatim}
+%mv texosquery-jre8.sh ~/bin/texosquery-jre8
+%\end{verbatim}
+%(You may omit the \file{.sh} files you don't need.)
+%The \file{.batch} files may be deleted.
+%\end{description}
+%
+%\item Move \file{texosquery.tex} to
+%\meta{TEXMF}\file{/tex/generic/texosquery/}
+%
+%\item\label{itm:cfg} Edit \file{texosquery.cfg} so that \cs{TeXOSInvokerName} is
+%defined to the application of your choice. For example, if you have
+%Java 8 installed:
+%\begin{verbatim}
+%\def\TeXOSInvokerName{texosquery-jre8}
+%\end{verbatim}
+%Or if you only have Java 5 or 6 installed:
+%\begin{verbatim}
+%\def\TeXOSInvokerName{texosquery-jre5}
+%\end{verbatim}
+%You can find out your Java version by running the following in your
+%command prompt or terminal:
+%\begin{verbatim}
+%java -version
+%\end{verbatim}
+%If the version number starts with \texttt{1.8} then you have Java 8
+%installed, if it starts with \texttt{1.7} then you have Java 7,
+%etc.
+%
+%\item Move \file{texosquery.cfg} to
+%\meta{TEXMF}\file{/tex/generic/texosquery/}
+%\item Move \file{texosquery.sty} to
+%\meta{TEXMF}\file{/tex/latex/texosquery/}
+%\item Move the \file{.jar} files to
+%\meta{TEXMF}\file{/scripts/texosquery/}
+%\end{enumerate}
+%
+%\subsection{Installation Test}
+%\label{sec:setuptest}
+%
+%To test the installation:
+%\begin{enumerate}
+%\item In the
+%\href{http://www.dickimaw-books.com/latex/novices/html/terminal.html}{command
+%prompt or terminal} do:
+%\begin{verbatim}
+%texosquery -b
+%\end{verbatim}
+%(Replace \texttt{texosquery} with the command that matches the value
+%of \cs{TeXOSInvokerName} in the \hyperref[itm:cfg]{\file{texosquery.cfg} file}
+%described in section~\ref{sec:setup}.)
+%The above command should display the system's
+%default locale. For me, this simply displays the line:
+%\begin{verbatim}
+%en-GB
+%\end{verbatim}
+%If you get an \texttt{Unknown option
+%\textquotesingle-b\textquotesingle} error, then your \gls{OS} is
+%picking up an old version of \app{texosquery}. Check the version
+%number with the \shortarg{v} switch.
+%\begin{verbatim}
+%texosquery -v
+%\end{verbatim}
+%If you get a \qt{command not found} or \qt{bad command or file name} error,
+%then recheck the installation steps in section~\ref{sec:setup} and
+%make sure that the executable file has been placed on your system's
+%path.
+%
+%If this test is successful, try the next step.
+%\item Create the following plain \TeX\ document called \file{test.tex}:
+%\begin{verbatim}
+%\input texosquery
+%\TeXOSQuery{\result}{-b}\result
+%\bye
+%\end{verbatim}
+%and compile using:
+%\begin{verbatim}
+%pdftex --shell-escape test
+%\end{verbatim}
+%
+%Alternatively, create the follow \LaTeX\ document called
+%\file{test.tex}:
+%\begin{verbatim}
+%\documentclass{article}
+%\usepackage{texosquery}
+%\begin{document}
+%\TeXOSQuery{\result}{-b}\result
+%\end{document}
+%\end{verbatim}
+%and compile using:
+%\begin{verbatim}
+%pdflatex --shell-escape test
+%\end{verbatim}
+%
+%In both cases, the resulting PDF file \file{test.pdf} should show the default
+%locale. If not check the transcript \file{test.log} which should
+%include something like \verb"(|texosquery -b)" or \verb"(|texosquery-jre8 -b)"
+%etc. If it simply has the line:
+%\begin{verbatim}
+%TeXOSQuery: texosquery -b
+%\end{verbatim}
+%(or similar) then the dry run mode was on, which means the shell escape wasn't
+%used. Check that the \longarg{shell-escape} switch was used when
+%calling \app{pdftex} or \app{pdflatex}.
+%
+%\TeX\ Live 2017 has added \app{texosquery-jre8} to the restricted
+%list, but you need to modify the configuration file to take
+%advantage of this. Make sure
+%that the line
+%\begin{verbatim}
+%\TeXOSQueryAllowRestricted
+%\end{verbatim}
+%hasn't been commented out in the \file{texosquery.cfg} file and try
+%the above example documents in restricted mode.
+%\end{enumerate}
+%
+%\subsection{Accessing file information}
+%\label{sec:fileaccess}
+%If an input file name is required (for example, with the
+%\longargfmt{pdfdate} argument described below) then the file may be in
+%the current working directory, relative to the current directory
+%(with forward slash \texttt{/} as the directory divider), an absolute path
+%(again with forward slash) or on \TeX's path (in
+%which case, \app{kpsewhich} is used to locate it). As from version
+%1.2, \app{texosquery} honours the \texttt{openin\_any} attribute set in the
+%\file{texmf.cnf} configuration file. This value is fetched using
+%\begin{verbatim}
+%kpsewhich -var-value=openin_any
+%\end{verbatim}
+%(You can find the configuration files using \texttt{kpsewhich -a
+%texmf.cnf}) For example, suppose the file \file{/tmp/.test}
+%exists. If the \texttt{openin\_any} attribute is set to \qt{\texttt{a}}
+%(any file), then (assuming the operating system allows read-access
+%to that file) the \app{texosquery} file-reading operations will
+%be permitted. For example
+%\begin{verbatim}
+%texosquery --pdfdate /tmp/.test
+%\end{verbatim}
+%will return the file modification date in PDF date-time format. However, if
+%\texttt{openin\_any} is set to \qt{\texttt{r}} (restricted), the read
+%access will be denied because the file is considered hidden so an
+%empty result is returned. Similarly, if \texttt{openin\_any} is set
+%to \qt{\texttt{p}} (paranoid), the read access will be denied again
+%because the file is hidden but also because the file has an
+%absolute path that isn't under \texttt{\$TEXMFOUTPUT} (assuming
+%that environment variable hasn't been set to \file{/tmp}).
+%
+%\subsection{MiKTeX}\label{sec:miktex}
+%MiKTeX doesn't support the \texttt{openin\_any}
+%variable so, if this is unset, \file{texosquery} will fallback on
+%\qt{\texttt{a}}.
+%
+%Note that MiKTeX disables piped input by default for security
+%reasons. Since \cs{TeXOSQuery} relies on piped input, you'll need
+%to enable it with \texttt{-{}-enable-pipes} when you run \TeX.
+%
+%\subsection{Restricted Mode}\label{sec:restricted}
+%TeX Live 2017 now has \app{texosquery-jre8} on the list of trusted
+%applications that may be run in restricted mode. In order to make
+%use of this, you need to set up your
+%\hyperref[itm:cfg]{configuration file} to use both
+%\app{texosquery-jre8} and to automatically switch off the dry run
+%mode if the restricted shell escape is detected. However, note that
+%the restricted mode has limitations on the characters allowed in
+%the shell escape for security reasons. This means that arguments
+%(such as file names) can't be quoted in restricted mode and
+%therefore arguments that contain spaces can't be delimited and will
+%cause problems. Either avoid spaces in file names or use the
+%unrestricted mode.
+%
+%\subsection{Return Values}
+%\label{sec:returnvalues}
+%The return values may include literal text where special characters
+%need to have their category code changed to 12 (for example, file names)
+%but the return values may also include \TeX\ code that needs to be
+%processed by \TeX, either during the shell escape or deferred for
+%later (such as date-time or numeric patterns). This means that the
+%result from the shell escape can't be automatically detokenized.
+%
+%Therefore, as from version 1.2, the return values include short
+%control sequences that are locally defined by \cs{TeXOSQuery} and
+%so are only valid within that command's scope. For example,
+%\cs{fcln} expands to a colon (\texttt{:}) with category code 12
+%whereas \cs{tcln} expands to a colon according to its current
+%meaning. Note that this has changed from earlier versions which
+%simply returned the actual characters, which may or may not have
+%had the category code set to 12 at the start of \cs{TeXOSQuery}. To
+%reproduce the original behaviour, use the compatibility mode
+%(\longarg{compatible}) with the level set to 0 or 1.
+%For the full list of shortcut commands, see the
+%definition of \hyperlink{enableshortcs}{\cs{@texosquery@enableshortcs}}.
+%
+%If the operating system uses a backslash \verb|\| as a directory
+%divider, returned path names will always replace this with a
+%forward slash \verb|/| (which is then converted to \cs{fslh}, as
+%described above). This is designed to make the result as compatible
+%as possible with \TeX\ file commands (such as \cs{input}) which
+%require a forward slash. However, for some systems,
+%further modification may need to be done. For example, with \TeX\
+%on \Index{Cygwin}, the Windows path name
+%\verb|c:\cygwin64\usr\local\texlive| may correspond to the \TeX\ path
+%name \file{/usr/local/texlive}. This path is returned by
+%\app{texosquery} as \file{c:/cygwin64/usr/local/texlive} (\cs{fcln} and
+%\cs{fslh} replaced for clarity). You can use
+%\longargfmt{strip-path-prefix} to strip the leading
+%\file{c:/cygwin64} for convenience (see section~\ref{sec:invocation}).
+%
+%The output produced by the \app{texosquery} application
+%will be returned using the system's default file
+%encoding. (For example, \gls{UTF}-8.) You will need to ensure that your \TeX\
+%document uses the same encoding if you want to typeset any of the
+%results that may contain non-ASCII characters. You can determine
+%the default encoding with \texttt{texosquery \shortarg{C}}, which is
+%formatted to match the options used by the \sty{inputenc} package.
+%(For example, \texttt{uft8} for \gls*{UTF}-8.)
+%
+%To test the file encoding rerun the plain \TeX\ or \LaTeX\ test
+%file in section~\ref{sec:setuptest} with
+%\shortarg{N} instead of \shortarg{b}. Most currency symbols are
+%outside the ASCII set, so this should return a non-ASCII character.
+%If you happen to have \$ as your currency,
+%then try \texttt{en-GB} or \texttt{en-IE} which have \pounds\ and
+%\texteuro, respectively. \LaTeX\ users may need to load
+%\sty{inputenc} and \sty{fontenc}. \XeLaTeX\ and \LuaLaTeX\ users may need to load
+%\sty{fontspec}.
+%
+%As from version 1.6, you can now override the default encoding
+%using the \longarg{encoding} option. This can be set on a document basis,
+%for example:
+%\begin{verbatim}
+%\usepackage[utf8]{inputenc}
+%\TeXOSQuery{\result}{--encoding UTF-8 -N}
+%\end{verbatim}
+%or for all documents by editing the \file{texosquery.cfg} file to
+%include the option in the invoker command.
+%
+%\subsection{Locales}
+%\label{sec:locales}
+%The options that have a locale identifier as an argument need the
+%identifier formatted as a \emph{regular}
+%\href{https://tools.ietf.org/html/bcp47}{IETF BCP 47 language tag} that uses
+%hyphens as separators. \gls{POSIX} style locales (with underscores
+%replaced by hyphens, for example \texttt{fr-BE.utf8@euro})
+%are only used as a return value in the \longarg{locale} and
+%\longarg{locale-lcs} options.
+%
+%\href{http://www.oracle.com/technetwork/java/javase/java8locales-2095355.html}{\textbf{Not
+%all locales are supported by Java.}}
+%For example, Irish is supported but Scottish and Welsh aren't supported by
+%the \gls{JRE}. The \gls{CLDR} can be accessed with Java~8, but
+%\href{http://openjdk.java.net/jeps/252}{the CLDR isn't
+%enabled by default}. It can be turned on using the system property
+%\texttt{java.locale.providers}, which may provide additional
+%support. For example, although Welsh isn't supported by the JRE,
+%it is supported with the \gls{CLDR}, so both Java~8 and the CLDR
+%locale provider are required for that language. The proposed
+%Java~9 should have the CLDR enabled by default.
+%
+%The bash script \file{texosquery-jre8}
+%automatically sets \texttt{java.locale.providers} to \texttt{CLDR,JRE}.
+%Alternatively, the \texttt{JAVA\_TOOL\_OPTIONS} environment variable
+%can be set to
+%\begin{verbatim}
+%-Djava.locale.providers=CLDR,JRE
+%\end{verbatim}
+%which will enable it for all installed Java applications.
+%If you need to set multiple options, these can be combined
+%in the value of \texttt{JAVA\_TOOL\_OPTIONS}.
+%For example
+%\begin{verbatim}
+%-Djava.locale.providers=CLDR,JRE -Dfile.encoding=UTF-8
+%\end{verbatim}
+%
+%You may find that the results are different depending on the data
+%provider. For example with \texttt{java.locale.providers} set to
+%\texttt{JRE,CLDR} then
+%\begin{verbatim}
+%texosquery -D en-GB
+%\end{verbatim}
+%displays the long date in the form \qt{06 November 2016} and the
+%medium date in the form \qt{06-Nov-2016}, but with the ordering
+%reversed to \texttt{CLDR,JRE} (so that the \gls{CLDR} is queried first)
+%then the long date is now in the form \qt{6 November 2016} and the
+%medium date is in the form \qt{6 Nov 2016}.
+%
+%Note that \app{texosquery} can only access locale information
+%provided by Java. For example, Java currently doesn't
+%provide any methods to access telephone codes.
+%
+%\subsection{Command line invocation}\label{sec:invocation}
+%The syntax for the command line invocation of \app{texosquery}
+%is:
+%\begin{definition}
+%texosquery \oarg{options} \meta{action} ...
+%\end{definition}
+%The syntax for \app{texosquery-jre8} is exactly the same except
+%for the application name:
+%\begin{definition}
+%texosquery-jre8 \oarg{options} \meta{action} ...
+%\end{definition}
+%Similarly for \app{texosquery-jre5}. (Bash users may need the
+%\file{.sh} extension if it wasn't removed from the script name
+%during the \hyperref[sec:setup]{installation setup}, but it's best
+%to remove it.) Available actions are
+%described below. At least one action is required.
+%
+%Available options (must come before actions):
+%\begin{description}
+%\item[\shortarg{h} or \longarg{help} or \shortarg{help}] Displays help message and
+%exits.
+%
+%\item[\shortarg{v} or \longarg{version} or \shortarg{version}] Displays version
+%information and exits.
+%
+%\item[\longarg{nodebug}] No debugging information. Only command
+%line syntax errors are written to STDERR. (Default.)
+%
+%\item[\longarg{debug} \oarg{n} or \shortarg{debug} \oarg{n}] Set the debugging level, where
+%\meta{n} is a non-negative integer.
+%If \meta{n} is omitted, 3 is assumed. If \meta{n} is 0, then
+%debugging information is suppressed (equivalent to
+%\longarg{nodebug}). If \meta{n} $\geq 1$, error messages are
+%written to STDERR. If \meta{n} $\geq 2$, any exceptions encountered
+%will additionally write the stack trace to STDERR. If \meta{n}
+%$\geq 3$ non-error informational messages are included.
+%
+%\item[\longarg{compatible} \meta{n} or \shortarg{compat} \meta{n}]
+%Set the compatibility mode.
+%The argument should be either a non-negative integer (0 for version
+%1.0, 1 for version 1.1, 2 for version 1.2 onwards) or the keyword \texttt{latest} to
+%indicate the latest version (default). Note that the compatibility
+%mode only affects the available actions and the display style of the
+%result, and does not change security features. For example, the check for the
+%\texttt{openin\_any} setting was only introduced to version 1.2, but this
+%is still checked even if the compatibility mode is set to 0 or 1.
+%
+%\item[\longarg{encoding} \meta{charset} or \shortarg{enc}
+%\meta{charset}]
+%(New to version 1.6.) This option may be used to override the
+%default file encoding. For example, if your native file encoding is
+%CP1250, but your document uses UTF-8 then you'll need to use
+%\longarg{encoding}\texttt{ UTF-8}.
+%
+%\item[\longarg{default-encoding} or \shortarg{defenc}]
+%(New to version 1.6.) This option is the default, but may
+%be used to cancel the effect of \longarg{encoding}. (For example, if
+%you have added \longarg{encoding} to the invoker command in the
+%\file{texosquery.cfg} file, but you want to cancel it in a
+%particular document.)
+%\end{description}
+%
+%The following options (introduced in v1.5) allow returned paths or URIs
+%to be altered (for example, if a mount point needs to be removed).
+%Note that these options only affect \emph{returned} paths, not
+%path names provided in the command line. The substitution is
+%performed after any backslash directory dividers
+%\verb|\| are changed to forward slashes \texttt{/} (if applicable) but before
+%non-alphanumerics are changed to control sequences (such as
+%\cs{fcln} or \cs{fslh}). If there's no match, the path is returned
+%without alteration.
+%
+%\begin{description}
+%\item[\longarg{strip-path-prefix} \meta{prefix} or \shortarg{sp}
+%\meta{prefix}]
+%Strip \meta{prefix} from the start of returned path names (but
+%not URIs, see below). For example:
+%\begin{verbatim}
+%texosquery --strip-path-prefix c:/cygwin64 -p article.cls
+%\end{verbatim}
+%This will strip the \texttt{c:/cygwin64} mount point. Note that
+%\meta{prefix} isn't an expression. For a regular expression, use
+%\longarg{replace-path}. (This option can't be
+%used with \longarg{replace-path}.)
+%
+%\item[\longarg{nostrip-path-prefix}]
+%Cancels the effect of \longarg{strip-path-prefix}.
+%
+%\item[\longarg{replace-path} \meta{regex} \meta{replacement}
+%or \shortarg{rp} \meta{regex} \meta{replacement}]
+%This replaces the \emph{first} occurrence of the regular
+%expression \meta{regex} in any returned path (but not URI) and replaces it with
+%\meta{replacement}.
+%
+%\item[\longarg{noreplace-path}]
+%Cancels the effect of the \longarg{replace-path}.
+%
+%\item[\longarg{strip-uri-prefix} \meta{prefix} or \shortarg{su}
+%\meta{prefix}]
+%Replace initial \texttt{file:/}\meta{prefix} with just
+%\texttt{file:/} from returned URIs. Note that
+%\meta{prefix} isn't an expression. For a regular expression, use
+%\longarg{replace-uri}. (This option can't be
+%used with \longarg{replace-uri}.)
+%
+%\item[\longarg{nostrip-uri-prefix}]
+%Cancels the effect of \longarg{strip-uri-prefix}.
+%
+%\item[\longarg{replace-uri} \meta{regex} \meta{replacement}
+%or \shortarg{ru} \meta{regex} \meta{replacement}]
+%This replaces the \emph{first} occurrence of the regular
+%expression \meta{regex} in any returned URI and replaces it with
+%\meta{replacement}. For example:
+%\begin{verbatim}
+%texosquery -ru file:/C: file://cgwin64/c -u filename
+%\end{verbatim}
+%
+%\item[\longarg{noreplace-uri}]
+%Cancels the effect of the \longarg{replace-uri}.
+%\end{description}
+%
+%If multiple actions are given, they will be processed in the
+%order specified in the command line invocation. Each result will
+%be displayed on a separate line. As from v1.1, if there are
+%multiple actions, each result will be grouped. This makes it easier
+%to process the results in \TeX. For example:
+%\begin{verbatim}
+%texosquery -l
+%\end{verbatim}
+%This just produces (for me):
+%\begin{verbatim}
+%en\fhyn GB\fdot utf8
+%\end{verbatim}
+%(which expands to \texttt{en-GB.utf8} when used with \cs{TeXOSQuery}) whereas
+%\begin{verbatim}
+%texosquery -l -o
+%\end{verbatim}
+%produces:
+%\begin{verbatim}
+%{en\fhyn GB\fdot utf8}
+%{Linux}
+%\end{verbatim}
+%To reproduce the v1.0 display use \longarg{compatible}\texttt{ 0}.
+%(This will also explicitly use the punctuation characters rather
+%than replacing them with the control sequence markup, such as
+%\cs{fhyn} or \cs{fdot}.)
+%
+%Note that unavailable information will produce an empty group.
+%For example (assuming \file{nofile} doesn't exist or doesn't have
+%read access):
+%\begin{verbatim}
+%texosquery -l -d nofile
+%\end{verbatim}
+%produces:
+%\begin{verbatim}
+%{en\fhyn GB\fdot utf8}
+%{}
+%\end{verbatim}
+%whereas
+%\begin{verbatim}
+%texosquery -d nofile
+%\end{verbatim}
+%just displays an empty line.
+%
+%If you're puzzled as to why an empty line has been returned, try
+%rerunning the command with \longarg{debug} for further information.
+%Available actions are listed below.
+%
+%\subsubsection{Action \shortarg{b} or \longarg{bcp47}}
+%(New to version 1.2.) This action displays the \gls{BCP}~47 language tag.
+%For example, my locale is \texttt{en-GB} (English in the United
+%Kingdom), so
+%\begin{verbatim}
+%texosquery -b
+%\end{verbatim}
+%Simply returns:
+%\begin{verbatim}
+%en\fhyn GB
+%\end{verbatim}
+%(which expands to \texttt{en-GB} with \cs{TeXOSQuery})
+%whereas a user whose default locale is set to Swiss German with the
+%new orthography would get:
+%\begin{verbatim}
+%de\fhyn CH\fhyn 1996
+%\end{verbatim}
+%(which expands to \texttt{de-CH-1996} with \cs{TeXOSQuery}).
+%
+%\subsubsection{Action \shortarg{L} or \longarg{locale}}
+%
+%This action displays the locale information in the \gls{POSIX} form
+%\begin{definition}
+%\meta{lang}-\meta{region}.\meta{codeset}@\meta{modifier}
+%\end{definition}
+%where \meta{lang} is the \gls{ISO} code for the language (e.g.\
+%\texttt{en}),
+%\meta{region} is the ISO code for the region (e.g.\ \texttt{GB}),
+%\meta{codeset} is the default code set (e.g.\ \texttt{UTF-8})
+% and \meta{modifier} is the modifier. Elements may be omitted
+% if unavailable. For example, \texttt{en-GB.UTF-8} has the
+% \meta{modifier} omitted, and \texttt{en} has all but the language
+% omitted. As above, the punctuation characters will actually be
+% returned using the control sequences \cs{fhyn} (hyphen), \cs{fdot}
+% (dot) and \cs{fatc} (at).
+%
+%If the \longarg{encoding} option is used, the codeset will reflect
+%that value.
+%
+%\subsubsection{Action \shortarg{l} or \longarg{locale-lcs}}
+%
+%This action is similar to \longarg{locale}, but the codeset is converted to
+%lower case and any hyphens are stripped. For example, if \longargfmt{locale}
+%returns \texttt{en-GB.UTF-8}, then \longargfmt{locale-lcs} would return
+%\texttt{en-GB.utf8}. As above, the punctuation characters will actually be
+% returned using the control sequences \cs{fhyn} (hyphen), \cs{fdot}
+% (dot) and \cs{fatc} (at).
+%Again, if the \longarg{encoding} option is used, the codeset will reflect
+%that value.
+%
+%\subsubsection{Action \shortarg{cs} or \longarg{codeset}}
+%
+%(New to version 1.6.) This action returns just the codeset.
+%For example, my default file encoding is \gls{UTF}-8, so
+%\begin{verbatim}
+%texosquery --codeset
+%\end{verbatim}
+%returns
+%\begin{verbatim}
+%UTF-8
+%\end{verbatim}
+%whereas
+%\begin{verbatim}
+%texosquery --encoding ISO-8859-1 --codeset
+%\end{verbatim}
+%returns
+%\begin{verbatim}
+%ISO-8859-1
+%\end{verbatim}
+%(In both the above examples, the hyphen character is actually
+%returned as the control sequence \cs{fhyn}.)
+%
+%\subsubsection{Action \shortarg{C} or \longarg{codeset-lcs}}
+%
+%(New to version 1.2.)
+%This action returns just the codeset converted to lower case with hyphens
+%stripped. For example, my default file encoding is \gls{UTF}-8, so
+%\begin{verbatim}
+%texosquery -C
+%\end{verbatim}
+%returns
+%\begin{verbatim}
+%utf8
+%\end{verbatim}
+%whereas
+%\begin{verbatim}
+%texosquery --encoding ISO-8859-1 -C
+%\end{verbatim}
+%returns
+%\begin{verbatim}
+%iso88591
+%\end{verbatim}
+%
+%\subsubsection{Action \shortarg{o} or \longarg{osname}}
+%
+%This action displays the operating system name. For example, for me this
+%produces:
+%\begin{verbatim}
+%Linux
+%\end{verbatim}
+%
+%\subsubsection{Action \shortarg{r} or \longarg{osversion}}
+%
+%This action displays the operating system version. For example, for me this
+%produces:
+%\begin{verbatim}
+%4\fdot 1\fdot 13\fhyn 100\fdot fc21\fdot x86\fusc 64
+%\end{verbatim}
+%(which expands to \texttt{4.1.13-100.fc21.x86\_64} when used with
+%\cs{TeXOSQuery}).
+%
+%\subsubsection{Action \shortarg{a} or \longarg{osarch}}
+%
+%This action displays the operating system architecture. For example, for me
+%this produces:
+%\begin{verbatim}
+%amd64
+%\end{verbatim}
+%
+%\subsubsection{Action \shortarg{M} or \longarg{date-time}}
+%(New to version 1.2.)
+%This action displays all the current date time data in a format
+%suitable for use in \cs{texosqueryfmtdatetime}. (See
+%section~\ref{sec:applydatetimepatterns}.)
+%
+%\subsubsection{Action \shortarg{Z} \oarg{locale} or
+%\longarg{time-zones} \oarg{locale}}
+%(New to version 1.2.)
+%This action displays all of the time zone mappings
+%for the given locale (or the default if \meta{locale} is omitted) in the format
+%\begin{definition}
+%\{\marg{id$_1$}\marg{short name}\marg{long
+%name}\marg{dst short name}\marg{dst long name}\}\ldots
+%\{\marg{id$_{n}$}\marg{short name}\marg{long
+%name}\marg{dst short name}\marg{dst long name}\}
+%\end{definition}
+%The \meta{id} is the unique label used by Java to identify the time
+%zone (such as \texttt{Europe/London}) as used in the time zone
+%information returned by \shortarg{M} (\longarg{date-time}).
+%
+%\subsubsection{Action \shortarg{n} or \longarg{pdfnow}}
+%
+%This action displays the current date and time in PDF format. For example
+%\begin{verbatim}
+%\pdfd \fcln 20160704131006\fpls 01\fapo 00\fapo
+%\end{verbatim}
+%This uses the shorthand tags \cs{pdfd}, \cs{fcln}, \cs{fpls} and
+%\cs{fapo} that
+%are locally redefined by
+%\cs{TeXOSQuery} to produce a \texttt{D}, a colon (\texttt{:}), a
+%plus sign (\texttt{+}) and an apostrophe (\texttt{\textquotesingle})
+%with the category code set to 12 to make it consistent with
+%\cs{pdfcreationdate}. This also allows
+%for situations where the punctuation characters have been made
+%active (for example, through \sty{babel}).
+%
+%Some, but not all, \TeX\ formats provide
+%\cs{pdfcreationdate}, which is more efficient than using the shell
+%escape, but this can be used
+%as a fallback method for those that don't (for example, \XeTeX).
+%
+%Note that versions 1.0 and 1.1 didn't use \cs{pdfd} etc but simply used
+%the actual characters. For example:
+%\begin{verbatim}
+%D:20160704131006+01'00'
+%\end{verbatim}
+%If you want to reproduce this format, use \longarg{compatible}
+%with the level set to 0 or 1.
+%
+%\subsubsection{Action \shortarg{d} \meta{file} or \longarg{pdfdate}
+%\meta{file}}
+%
+%This action displays the last modified time stamp of the given file in PDF format or a blank line
+% if the file doesn't exist or the file permissions prohibit this
+% action.
+%Again some, but not all, \TeX\ formats provide
+%\cs{pdffilemoddate}\marg{file}, which is more efficient than using
+%the shell escape.
+%
+%As with \longarg{pdfnow} this now uses \cs{pdfd} etc which are converted
+%by \cs{TeXOSQuery} to characters with the category code set to 12.
+%
+%This action obeys the \texttt{openin\_any} setting, so if access to
+%\meta{file} is forbidden by this setting, the result will be empty.
+%
+%\subsubsection{Action \shortarg{s} \meta{file} or \longarg{filesize}
+%\meta{file}}
+%
+%This action displays the size in bytes of the given file or an
+%empty string if the file doesn't exist or the file permissions prohibit this
+%action. Some, but not all, \TeX\ formats provide
+%\cs{pdffilesize}\marg{file}, which is more efficient than using
+%the shell escape.
+%
+%This action obeys the \texttt{openin\_any} setting, so if access to
+%\meta{file} is forbidden by this setting, the result will be empty.
+%
+%\subsubsection{Action \shortarg{c} or \longarg{cwd}}
+%
+%This action displays the current working directory. This obeys the
+%\texttt{openin\_any} setting, so this action will return an empty
+%string if this file information is forbidden by that setting.
+%
+%\subsubsection{Action \shortarg{m} or \longarg{userhome}}
+%
+%This action displays the user's home directory. This obeys the
+%\texttt{openin\_any} setting, so this action will return an empty
+%string if this file information is forbidden by that setting.
+%
+%\subsubsection{Action \shortarg{t} or \longarg{tmpdir}}
+%
+%This action displays the temporary directory. This obeys the
+%\texttt{openin\_any} setting, so this action will return an empty
+%string if this file information is forbidden by that setting.
+%
+%\subsubsection{Action \shortarg{i} \meta{sep} \meta{dir} \oarg{sort} or \longarg{list}
+%\meta{sep} \meta{dir} \oarg{sort}}
+%
+%This action lists all files in the given directory with the output on a single
+%line using \meta{sep} as the separator between entries. Note that
+%the list doesn't include the full path, just the file names.
+%
+%\begin{important}
+%As from v1.2, new restrictions have been placed on the value of \meta{dir}
+%for security reasons. For all three applications,
+%\app{texosquery-jre8}, \app{texosquery} and \app{texosquery-jre5},
+%the \hyperref[sec:fileaccess]{\texttt{openin\_any} setting} is checked. If read access to
+%\meta{dir} is forbidden by the \texttt{openin\_any} setting, then
+%this action returns an empty string. \emph{Additionally},
+%regardless of \texttt{openin\_any}, the more restrictive
+%applications, \app{texosquery-jre8} and \app{texosquery}, prohibit a
+%value of \meta{dir} that's outside the current working directory
+%path (e.g.\ \texttt{..}) or that has no parent directory (e.g.\ \texttt{/}).
+%Both \app{texosquery-jre8} and \app{texosquery} check the
+%\emph{canonical path} of \meta{dir}, so if \meta{dir} is a symbolic
+%link, the target path is checked.
+%
+%This is a security feature to prevent
+%any malicious code that might try to recursively list the contents
+%of the entire filing system, which would hog resources, or that
+%might try to discover files outside the current working directory. An
+%exception is made for \app{texosquery-jre5} since that application
+%is already considered insecure (due to Java 5 and 6 being long
+%deprecated), so if you really need \meta{dir} as, say \texttt{..} (the
+%parent directory) or \texttt{/} (the root directory), you can use
+%\app{texosquery-jre5} (by redefining \cs{TeXOSInvokerName} before
+%using \cs{TeXOSQuery}) although this isn't recommended. It will
+%still obey the \texttt{openin\_any} setting, so the listing still
+%won't work with \app{texosquery-jre5} if the \texttt{openin\_any}
+%setting is set to \texttt{p} (paranoid).
+%\end{important}
+%
+%As from version 1.2, there is now an optional argument \meta{sort},
+%which indicates how the returned list should be sorted. If omitted
+%\texttt{default} is assumed. Available values of \meta{sort}:
+%\begin{description}
+%\item[\texttt{default}] Use the default order. This is typically in
+%alphabetical order, but depends on the operating system or
+%\gls{JRE}.
+%\item[\texttt{date-ascending}] Order by file modified date from
+%oldest to newest. This option has synonyms \texttt{date} and
+%\texttt{date-asc}.
+%\item[\texttt{date-descending}] Order by file modified date from
+%newest to oldest. You may use the shorter \texttt{date-des}
+%value instead.
+%\item[\texttt{size-ascending}] Order by file size from
+%smallest to largest. This option has synonyms \texttt{size} and
+%\texttt{size-asc}.
+%\item[\texttt{size-descending}] Order by file size from
+%largest to smallest. You may use the shorter \texttt{size-des}
+%value instead.
+%\item[\texttt{name-ascending}] Order by file name (case-sensitive)
+%alphabetically. This option has synonyms \texttt{name} and
+%\texttt{name-asc}.
+%\item[\texttt{name-descending}] Order by file name (case-sensitive)
+%in reverse alphabetic order. You may use the shorter
+%\texttt{name-des} value instead.
+%\item[\texttt{iname-ascending}] Order by file name (case-insensitive)
+%alphabetically. This option has synonyms \texttt{iname} and
+%\texttt{iname-asc}.
+%\item[\texttt{iname-descending}] Order by file name (case-insensitive)
+%in reverse alphabetic order. You may use the shorter
+%\texttt{iname-des} value instead.
+%\item[\texttt{ext-ascending}] Order by file extension (case-sensitive)
+%alphabetically. If files have the same extension, they are ordered
+%by name. This option has synonyms \texttt{ext} and
+%\texttt{ext-asc}.
+%\item[\texttt{ext-descending}] Order by file extension (case-sensitive)
+%in reverse alphabetic order. If files have the same extension, they are
+%ordered by name (reverse alphabetic order). You may use the shorter
+%\texttt{ext-des} value instead.
+%\end{description}
+%
+%This action obeys the \texttt{openin\_any} setting for all the
+%listed files as well as for the directory \meta{dir}, so if access to
+%a file in the directory is forbidden, the file will be omitted from
+%the list. (This action is equivalent to the following with
+%\meta{regex} set to \texttt{.*} to match all files.)
+%
+%If you want to excluded hidden dot files (where they aren't
+%automatically excluded by \texttt{openin\_any}), use the
+%\longarg{filterlist} action described below with \meta{regex} set
+%to \verb|[^\.].*|. (Remember that you'll need to use \cs{string}
+%when using the shell escape, as noted below.)
+%
+%\begin{important}
+%Unlike most of the return values the \meta{sep} part here isn't
+%escaped, so take care if \meta{sep} contains any commands. For
+%example, if you want to use \verb|\\| as the separator, you'll need
+%to use \verb|\string\noexpand\string\\| in the \meta{sep} part
+%within \cs{TeXOSQuery}.
+%\end{important}
+%
+%For example:
+%\begin{verbatim}
+%\TeXOSQueryFileList{\result}{\string\noexpand\string\\}{.}
+%\end{verbatim}
+%calls (through the shell escape):
+%\begin{verbatim}
+%texosquery -i '\noexpand\\' '.'
+%\end{verbatim}
+%(the two \cs{string} commands have detokenized their arguments)
+%so \app{texosquery} uses \verb|\noexpand\\| as the separator in the
+%returned list, but this list is expanded as it's read in. However
+%\cs{noexpand} prevents the \verb|\\| from being expanded, so the
+%separator becomes just \verb|\\| which may be (re)defined before the
+%resulting list is processed.
+%
+%Note that \cs{TeXOSQueryFileList} automatically adds the single
+%quotes around the arguments. If \cs{TeXOSQuery} is used explicitly,
+%these quotes would need to be added as appropriate.
+%
+%\subsubsection{Action \shortarg{id} \meta{sep} \meta{dir}
+%\oarg{sort} or \longarg{list-dir}
+%\meta{sep} \meta{dir} \oarg{sort}}
+%
+%This action is like \longarg{list} but only includes sub-directories
+%of \meta{dir}. The caveats and security notes for \longarg{list} also apply
+%here.
+%
+%\subsubsection{Action \shortarg{ir} \meta{sep} \meta{dir}
+%\oarg{sort} or \longarg{list-regular}
+%\meta{sep} \meta{dir} \oarg{sort}}
+%
+%This action is like \longarg{list} but only includes regular files.
+%The caveats and security notes for \longarg{list} also apply here.
+%
+%\subsubsection{Action \shortarg{f} \meta{sep} \meta{regex} \meta{dir}
+%\oarg{sort} or \longarg{filterlist} \meta{sep} \meta{regex} \meta{dir}
+%\oarg{sort}}
+%
+%This action is like \longarg{list} but only lists those files whose name
+%matches the
+%\href{http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html}{regular expression} given in \meta{regex}. Note that
+%since this uses Java's \texttt{String.matches} method this tests for a
+%\emph{complete} match on the file name (not including directory path). For example,
+%if \meta{regexp} is \texttt{foo.*}, it will only match files whose
+%name starts with \texttt{foo} (for example, \texttt{foobar} will
+%match but \texttt{barfoo} won't). Use \texttt{.*foo.*} to match all
+%files that contain \texttt{foo} in the name (so \texttt{foobar} and
+%\texttt{barfoo} will both match).
+%
+%\begin{important}
+%You can't have an empty regular expression. You can use the regular
+%expression \texttt{.*} to match all files (which is what
+%\longarg{list} does).
+%\end{important}
+%
+%As from version 1.2, this action now has an optional argument
+%\meta{sort}, which indicates how to sort the returned list. The
+%available values for \meta{sort} are the same as for
+%\longarg{list}, described above.
+%
+%The caveats and security notes for \longarg{list} also apply here.
+%
+%\subsubsection{Action \shortarg{fd} \meta{sep} \meta{regex} \meta{dir}
+%\oarg{sort} or \longarg{filterlist-dir} \meta{sep} \meta{regex} \meta{dir}
+%\oarg{sort}}
+%
+%This action is like \longarg{filterlist} but only includes
+%sub-directories of \meta{dir}.
+%
+%The caveats and security notes for \longarg{list} also apply here.
+%
+%\subsubsection{Action \shortarg{fr} \meta{sep} \meta{regex} \meta{dir}
+%\oarg{sort} or \longarg{filterlist-regular} \meta{sep} \meta{regex} \meta{dir}
+%\oarg{sort}}
+%
+%This action is like \longarg{filterlist} but only includes
+%regular files.
+%
+%The caveats and security notes for \longarg{list} also apply here.
+%
+%\subsubsection{Action \shortarg{w} \meta{sep} \meta{regex} \meta{dir}
+%\oarg{sort} or \longarg{walk}
+%\meta{sep} \meta{regex} \meta{dir} \oarg{sort}}
+%
+%(New to version 1.2. Not available with \app{texosquery-jre5}.)
+%
+%This action starts from the directory \meta{dir} \emph{which must
+%be on the current working directory's path} and returns a list
+%separated by \meta{sep} of all the regular files whose basename matches
+%the regular expression \meta{regex} (as for the filtered file
+%listings described above), recursively descending
+%sub-directories. Any files or sub-directories that are hidden, unreadable
+%or symbolic links are skipped. The list is sorted
+%according to \meta{sort}, which is as for the file listing actions
+%described above. Note that \meta{dir} is first converted to its
+%canonical path, so if \meta{dir} is a symbolic link, the
+%security check will test if the \emph{target} path is on the
+%current working directory path.
+%
+%As with the above file listings, the separator \meta{sep} isn't
+%escaped so take care if \meta{sep} contains any commands. The
+%resulting list will consist of paths relative to \meta{dir}.
+%
+%\begin{important}
+%This action requires at least Java 7 so it's not available with
+%\file{texosquery-jre5.jar}.
+%\end{important}
+%
+%\subsubsection{Action \shortarg{u} \meta{file} or \longarg{uri}
+%\meta{file}}
+%
+%This action displays the URI of the given file or an empty string if the file
+%doesn't exist or if the file permissions or the
+%\texttt{openin\_any} setting prohibit read access.
+%
+%\subsubsection{Action \shortarg{p} \meta{file} or \longarg{path}
+%\meta{file}}
+%
+%This action displays the canonical path of the given file or an
+%empty string if the file doesn't exist or if the file permissions
+%or the \texttt{openin\_any} setting prohibit this action.
+%
+%\subsubsection{Action \shortarg{e} \meta{file} or \longarg{dirname}
+%\meta{file}}
+%
+%(New to v1.1.)
+%This action displays the canonical path of the given file's parent (that is, the
+%directory containing \meta{file}) or an empty string if
+%the file doesn't exist or if the file permissions or the
+%\texttt{openin\_any} setting prohibits this
+%action. Note that this is different to the Unix-like
+%\app{dirname} command, which will return a relative path if
+%\meta{file} isn't an absolute path.
+%
+%\subsubsection{Action \shortarg{N} \oarg{language tag} or \longarg{numeric}
+%\oarg{language tag}}
+%
+%(New to v1.2.) This action displays:
+%\begin{definition}
+%\marg{locale tag}\marg{group sep}\marg{decimal sep}\marg{exp
+%sep}\marg{use group}\marg{currency code}\marg{regional currency
+%code}\marg{currency sym}\marg{\TeX\ currency}\marg{currency sep}
+%\end{definition}
+%for the \meta{language tag} given in the optional argument. If omitted, the
+%default locale is assumed. The returned values are:
+%\begin{itemize}
+%\item \meta{tag} the language tag.
+%\item \meta{group sep} the numeric group separator.
+%\item \meta{decimal sep} the decimal separator.
+%\item \meta{exp sep} the exponent separator.
+%\item \meta{use group} \texttt{1} if the locale uses number
+%grouping otherwise \texttt{0}.
+%\item \meta{currency code} the ISO 4217 currency code.
+%\item \meta{regional currency code} either the ISO 4217 currency
+%code or an unofficial code. The only unofficial codes returned are:
+%GGP (Guernsey pound), JEP (Jersey pound), IMP (Isle of Man
+%pound), KID (Kiribati dollar) and TVD (Tuvaluan dollar).
+%\item \meta{currency sym} the currency symbol. (This may sometimes be
+%the same as \meta{currency code}.) Non-ASCII characters will be
+%marked up with \cs{twrp} (see below).
+%\item \meta{\TeX\ currency} the currency symbol using \TeX\ code
+%provided by \sty{texosquery}.
+%This is obtained by substituting known Unicode
+%currency symbols occurring in \meta{currency sym} with
+%\cs{texosquerycurrency}\marg{xxx}, which expands to the control
+%sequence given by the name \texttt{texosquerycurrency}\meta{xxx}.
+%These commands are defined in \file{texosquery.tex}. Since there are no generic \TeX\ commands
+%available for all these symbols (except \$), these commands will need to be
+%redefined as appropriate but are provided in the event that there's
+%no UTF-8 support. There is a limited check for some known currency
+%commands, such as \cs{texteuro} or \cs{euro}, but if an appropriate
+%currency command can't be found, the
+%\cs{texosquerycurrency}\meta{xxx} commands will be defined to
+%simply the currency label (usually the same as the \meta{xxx}
+%part).
+%
+%\item \meta{currency sep} the currency decimal separator.
+%\end{itemize}
+%The language tag should conform to \gls{IETF} \gls{BCP}~47. See
+%\url{http://docs.oracle.com/javase/8/docs/api/java/util/Locale.html}
+%for further details. If you are using \app{texosquery-jre5},
+%only the language, region and variant elements will be recognised
+%since the language tag support was introduced in Java~7.
+%
+%For example:
+%\begin{verbatim}
+%texosquery -N en-GB
+%\end{verbatim}
+%produces
+%\begin{verbatim}
+%{en-GB}{,}{.}{E}{1}{GBP}{GBP}{\twrp{£}}{\texosquerycurrency{pound}}{.}
+%\end{verbatim}
+%The \cs{twrp} command is used by \app{texosquery} to markup a
+%non-\gls{ASCII} character. This command is one of the shorthands only
+%defined within \cs{TeXOSQuery}. In this case it's a shortcut for
+%the command \cs{texosquerynonasciiwrap}. By default this just does its
+%argument, but it may be redefined to perform some other action such
+%as converting from one encoding to another.
+%
+%In most cases the \meta{regional currency code} will be the same
+%as \meta{currency} code. A few non-ambiguous unofficial codes
+%are known by \app{texosquery} and may be used if the country code
+%is recognised. For example,
+%\begin{verbatim}
+%texosquery -N en-IM
+%\end{verbatim}
+%produces
+%\begin{verbatim}
+%{en-IM}{,}{.}{E}{1}{GBP}{IMP}{M\twrp{£}}{M\texosquerycurrency{pound}}{.}
+%\end{verbatim}
+%
+%If Java doesn't support the given locale, the currency code will
+%appear as \texttt{XXX} with the symbol \textcurrency\ (generic
+%currency sign).
+%
+%\begin{important}
+%This option and the following (\longarg{locale-data}) are best used
+%with \XeTeX\ or \LuaTeX\ to deal with the non-\gls{ASCII} characters.
+%Make sure the file encoding used by Java matches the \TeX\ file.
+%\end{important}
+%
+%(See section~\ref{sec:locales} for the difference in locale
+%providers.)
+%
+%\subsubsection{Action \shortarg{D} \oarg{language tag} or
+%\longarg{locale-data} \oarg{language tag}}
+%
+%(New to v1.2.) This action provides more
+%extensive information than \longarg{numeric}.
+%The result has nested groups to assist parsing.
+%Again the \meta{language tag} may be omitted. For example,
+%\begin{verbatim}
+%texosquery --locale-data
+%\end{verbatim}
+%For the default locale or
+%\begin{verbatim}
+%texosquery --locale-data en-GB
+%\end{verbatim}
+%for the locale identified by \texttt{en-GB}.
+%As with all the other actions, the result is written to STDOUT on a
+%single line. Its overall length and the use of the shortcut
+%commands used by \app{texosquery} to markup certain elements mean
+%that it's not particularly human-readable, but it's designed to be
+%easy for \TeX\ to interpret. The information is returned in the
+%following format:
+%\begin{definition}
+%\marg{locale block}\marg{current date block}\marg{date pattern
+%block}\marg{current time block}\allowbreak\marg{time pattern
+%block}\marg{current date time block}\marg{date time pattern block}\marg{days
+%of the week block}\marg{abbreviated dates of the week
+%block}\marg{month names block}\marg{abbreviated month names
+%block}\marg{standalone days of the week block}\marg{abbreviated
+%standalone days of the week block}\marg{standalone month names
+%block}\marg{abbreviated standalone month names block}\marg{numeric
+%block}\marg{numeric patterns block}
+%\end{definition}
+%There may seem to be some repetition here with the month and week
+%day names, but with \app{texosquery-jre8}, the second set are the
+%standalone version (for example, for a column header). In some
+%languages, these may be different from the names used in the date
+%format. Since this is new to Java 8, it's not supported in
+%\file{texosquery.jar} or \file{texosquery-jre5.jar} and they simply reproduce
+%the non-standalone names.
+%
+%The information supplied with this option is quite complex,
+%but it's used by the \sty{locale} package to set up all the
+%required information for each locale used in the document.
+%Any non-\gls{ASCII} characters are marked up with \cs{twrp},
+%which is locally defined by \cs{TeXOSQuery} to expand to
+%\cs{texosquerynonasciiwrap}. This may be redefined to deal with the
+%characters if necessary. For example, if the character needs to be
+%converted from one encoding to another.
+%
+%The blocks are:
+%\begin{description}
+%\item \meta{locale block}
+%
+%The locale information in the form:
+%\begin{definition}
+%\marg{tag}\marg{language name}\marg{locale language
+%name}\marg{region name}\marg{locale region name}\marg{variant
+%name}\marg{locale variant name}
+%\end{definition}
+%The \meta{tag} is the language tag (the same format as \longarg{bcp47}).
+%
+%The \meta{language name} is the language name in the operating
+%system's default locale.
+%
+%The \meta{locale language name} is the language name in
+%the locale's language.
+%
+%For example, my locale is \texttt{en-GB}, so if I use
+%\begin{verbatim}
+%texosquery -D en-GB
+%\end{verbatim}
+%then both \meta{language name} and \meta{locale language name} will
+%be \texttt{English}, but if I use:
+%\begin{verbatim}
+%texosquery -D fr-GB
+%\end{verbatim}
+%then \meta{language name} will be \texttt{French} and the \meta{locale
+%language name} will be \texttt{fran\cs{twrp}\char`\{\c{c}\char`\}ais} (note the non-ASCII
+%character has been marked up). The locale tag \verb|fr-GB|
+%indicates that I'm writing in French but I'm in the United Kingdom
+%(so the currency should be GBP).
+%
+%The \meta{region name} is the region's name in the operating
+%system's default language.
+%
+%The \meta{locale region name} is the region's name in the locale's language.
+%
+%So for me with \texttt{-D en-GB} I get
+%\verb|United\tspc Kingdom| for both \meta{region name} and
+%\meta{locale region name}. This illustrates another of the
+%shorthand commands that \app{texosquery} uses that's only locally
+%defined within \cs{TeXOSQuery}. In this case, \cs{tspc} just expands
+%to a space. This is used to avoid accidentally discarding any
+%intentional spaces that might follow a command name or any
+%intentional consecutive spaces.
+%
+%If, however, I use \texttt{-D fr-GB} I still get
+%\verb|United\tspc Kingdom| in \meta{region name}, but \meta{locale
+%region name} is now \verb|Royaume-Uni|.
+%
+%The \meta{variant name} is the language's variant. For example,
+%with \verb|de-CH-1996| (Swiss German using the new orthography),
+%the variant is 1996. There's no variant in \verb|en-GB| so this
+%value is empty for me.
+%
+%The \meta{locale variant name} is the variant in the locale's
+%language. In the case of \verb|de-CH-1996| this is still 1996.
+%
+%\item \meta{current date block}
+%
+%This returns the current date in four different formats and also an
+%integer that indicates the first day of the week in the given
+%locale:
+%\begin{definition}
+%\marg{full date}\marg{long date}\marg{medium date}\marg{short
+%date}\marg{first day}
+%\end{definition}
+%The actual date formats depend on the locale. For example, with
+%\texttt{en-GB} the \meta{full date} is (assuming today is
+%2016-11-08):
+%\begin{verbatim}
+%Tuesday,\tspc 8\tspc November\tspc 2016
+%\end{verbatim}
+%(Tuesday, 8 November 2016).
+%The \meta{long date} is
+%\begin{verbatim}
+%08\tspc November\tspc 2016
+%\end{verbatim}
+%(08 November 2016).
+%The \meta{medium date} is
+%\begin{verbatim}
+%08\thyn Nov\thyn 2016
+%\end{verbatim}
+%(08-Nov-2016).
+%The \meta{short date} is
+%\begin{verbatim}
+%08\tslh 11\tslh 16
+%\end{verbatim}
+%(08/11/2016).
+%Note that there's a difference between using the \gls{CLDR} locale data
+%and the JRE data. If I'm using the Java~7 compatible
+%\app{texosquery.jar} which only uses JRE locale data, then I get
+%the above results, but if I use the bash script \file{texosquery-jre8} which
+%sets \texttt{java.locale.providers} to \texttt{CLDR,JRE} then I get
+%a slightly different result. The \meta{long date} is
+%\begin{verbatim}
+%8\tspc November\tspc 2016
+%\end{verbatim}
+%(8 November 2016)
+%and the \meta{medium date} is
+%\begin{verbatim}
+%8\tspc Nov\tspc 2016
+%\end{verbatim}
+%(8 Nov 2016).
+%The \meta{medium date} may be numeric or may be an abbreviated
+%form of \meta{long date}, depending on the language and the locale
+%provider. Some languages aren't supported by \gls{JRE} but are supported
+%by \gls{CLDR}. (Some aren't supported by either, but there's a
+%chance that those languages will eventually be added to the CLDR.)
+%For example, if I use \texttt{-D cy-GB} with the JRE as the locale
+%provider I just get the \texttt{en-GB} dates, but if I use the CLDR
+%provider I get Welsh dates.
+%
+%Note that the proposed Java~9 should automatically use the CLDR as
+%the locale provider, which is being increasingly adopted by
+%applications as a common data repository.
+%
+%The first day of the week index is zero-based starting with Monday.
+%This is done in order to be compatible with \sty{pgfcalendar}. For
+%example, with \texttt{-D en-GB} \meta{first day} is 0 (Monday), but
+%with \texttt{pt-BR} \meta{first day} is 6 (Sunday). The
+%\sty{locale} package provides a way of converting the index to
+%Monday=1 or Sunday=1 indexing.
+%
+%\item \meta{date pattern block}
+%
+%The pattern used to format the full date, long date, medium
+%date and short date. This is in the form:
+%
+%\marg{full pattern}\marg{long pattern}\marg{medium
+%pattern}\marg{short pattern}
+%
+%Each pattern uses shorthand mark-up that's only locally defined
+%within \cs{TeXOSQuery}. These short commands are expanded to longer
+%commands provided by \file{texosquery.tex} to avoid name clashing
+%with other packages. When used directly in the document text, these
+%expand to reproduce the pattern.
+%
+%For example, with \texttt{-D en-GB} I get the following
+%pattern for the short date:
+%\begin{verbatim}
+%\patdtf{2}{d}\tslh \patdtf{2}{M}\tslh \patdtf{2}{y}
+%\end{verbatim}
+%When parsed by \cs{TeXOSQuery}, this is internally converted to
+%\begin{verbatim}
+%\texosquerydtf{2}{d}/\texosquerydtf{2}{M}/\texosquerydtf{2}{y}
+%\end{verbatim}
+%But default this simply expands to \texttt{dd/MM/yy} but may be
+%used in the first argument of \cs{texosqueryfmtdatetime}. See
+%sections~\ref{sec:patternformats} and \ref{sec:applydatetimepatterns} for
+%further details.
+%
+%\item \meta{current time block}
+%
+%The current time provided in various formats suitable to the
+%given locale:
+%\begin{definition}
+%\marg{full time}\marg{long time}\marg{medium time}\marg{short time}
+%\end{definition}
+%As with the current date, the actual format depends on the locale
+%and the locale provider. For example, with \texttt{en-GB} I get:
+%\begin{verbatim}
+%{15:59:41\tspc o\csq clock\tspc GMT}{15:59:41\tspc GMT}{15:59:41}{15:59}
+%\end{verbatim}
+%with the \gls{JRE}. If I switch to \gls{CLDR} (in the bash script \file{texosquery-jre8}) I get:
+%\begin{verbatim}
+%{16:00:51\tspc Greenwich\tspc Mean\tspc Time}{16:00:51\tspc GMT}{16:00:51}{16:00}
+%\end{verbatim}
+%
+%\item \meta{time pattern block}
+%
+%The pattern used to format the full time, long time, medium
+%time and short time.
+%
+%\marg{full time pattern}\marg{long time pattern}\marg{medium time
+%pattern}\marg{short time pattern}
+%
+%Again, when used with \cs{TeXOSQuery}, the short commands, such as
+%\cs{patdtf}, are internally converted.
+%They're not defined outside that scope.
+%
+%\item \meta{current date time block}
+%
+%The current date and time provided in various formats suitable to the
+%given locale:
+%\begin{definition}
+%\marg{full date time}\marg{long date time}\marg{medium date
+%time}\marg{short date time}
+%\end{definition}
+%This may simply be the date and time from above separated by
+%a space.
+%
+%\item \meta{date time pattern block}
+%
+%The pattern used to format the full date time, long date time, medium
+%date time and short date time. This may simply be the date and
+%time patterns from above separated by a space.
+%\begin{definition}
+%\marg{full date time pattern}\marg{long date time
+%pattern}\marg{medium date time pattern}\marg{short date time pattern}
+%\end{definition}
+%
+%\item \meta{days of the week block}
+%
+%The week day names (starting with Monday for consistency with
+%\sty{pgfcalendar}) in the locale's language. Non-\gls{ASCII} characters are marked up with
+%\cs{twrp}.
+%\begin{definition}
+%\marg{Monday}\marg{Tuesday}\marg{Wednesday}\marg{Thursday}\marg{Friday}\marg{Saturday}\marg{Sunday}
+%\end{definition}
+%\item \meta{abbreviated days of the week block}
+%
+%As above, but abbreviated.
+%
+%\item \meta{month names block}
+%
+%The month names.
+%
+%\item \meta{abbreviated month names block}
+%
+%The abbreviated month names.
+%
+%\item \meta{standalone days of the week block}
+%
+%The week day names when used in a standalone context (for example, a
+%column header). This may be the same as the earlier \meta{days of
+%the week block} (and will be the same for \file{texosquery.jar} and
+%\file{texosquery-jre5.jar}). The standalone support was
+%introduced to Java~8.
+%
+%\item \meta{abbreviated standalone days of the week block}
+%
+%As above, but abbreviated.
+%
+%\item \meta{standalone month names block}
+%
+%The month names when used in a standalone context (for example, a
+%column header). This may be the same as the earlier \meta{month
+%names block} (and will be the same for \file{texosquery.jar} and
+%\file{texosquery-jre5.jar}). The standalone support was
+%introduced to Java~8.
+%
+%\item \meta{abbreviated standalone month names block}
+%
+%As above but abbreviated.
+%
+%\item \meta{numeric block}
+%
+%The numeric data similar to \longarg{numeric} but it's
+%missing the \meta{tag} (which is provided in the earlier
+%\meta{locale block}) and there are two extra items:
+%\begin{definition}
+%\marg{group sep}\marg{decimal sep}\marg{exp
+%sep}\marg{use group}\marg{currency code}\marg{regional currency
+%code}\marg{currency sym}\marg{currency tex}\marg{currency sep}
+%\marg{percent sym}\marg{per mill sym}
+%\end{definition}
+%See above for the elements that are also provided in
+%\longarg{numeric}. The additional elements are \meta{percent sym}
+%and \meta{per mill sym}, which are the percent and
+%per-mill symbols, respectively. The percent symbol \verb|%| has its
+%category code changed to 12 by \cs{TeXOSQuery}. As with other
+%non-ASCII characters, the per-mill symbol will be marked up with
+%\cs{twrp}.
+%
+%\item \meta{numeric patterns block}
+%
+%The patterns used to format decimals, integers, currency and
+%percentages.
+%\begin{definition}
+%\marg{decimal pattern}\marg{integer pattern}\marg{currency
+%pattern}\marg{percentages pattern}
+%\end{definition}
+%As with the date and time patterns, when used with \cs{TeXOSQuery}, the
+%short commands, such as \cs{patdgt}, are internally converted.
+%They're not defined outside that scope.
+%
+%If a pattern is used directly in the text, it will expand to the
+%original pattern padded to ten digits. (Eleven digit integers are outside
+%\TeX's maximum number range.)
+%
+%Any of these numeric patterns may be used in the first argument of
+%the low-level user command \cs{texosqueryfmtnumber} described in
+%section~\ref{sec:applynumpatterns}. This command uses the following
+%macros:
+%\begin{definition}
+%\cs{texosquerypatfmtcurrencysign}
+%\end{definition}
+%The currency sign (defaults to \verb|\$|). For example, when parsing the previous
+%\meta{numeric block}, this command can be redefined to the \marg{currency sym} or
+%\marg{currency tex} elements.
+%
+%\begin{definition}
+%\cs{texosquerypatfmtgroupsep}
+%\end{definition}
+%The group separator (defaults to \verb|,|). For example, when parsing the previous
+%\meta{numeric block}, this command can be redefined to the
+%\marg{group sep} element.
+%
+%\begin{definition}
+%\cs{texosquerypatfmtdecsep}
+%\end{definition}
+%The decimal separator (defaults to \verb|.|). For example, when parsing the previous
+%\meta{numeric block}, this command can be redefined to the
+%\marg{dec sep} element.
+%
+%\begin{definition}
+%\cs{texosquerypatfmtcurdecsep}
+%\end{definition}
+%The monetary decimal separator (defaults to \verb|.|). For example, when parsing the previous
+%\meta{numeric block}, this command can be redefined to the
+%\marg{currency sep} element.
+%
+%\begin{definition}
+%\cs{texosquerypatfmtexp}
+%\end{definition}
+%The exponent sign (defaults to \texttt{E}). For example, when parsing the previous
+%\meta{numeric block}, this command can be redefined to the
+%\marg{exp sep} element.
+%
+%\begin{definition}
+%\cs{texosquerypatfmtpercentsign}
+%\end{definition}
+%The percent symbol. For example, when parsing the previous
+%\meta{numeric block}, this command can be redefined to the
+%\marg{percent sym} element.
+%
+%\begin{definition}
+%\cs{texosquerypatfmtpermillsign}
+%\end{definition}
+%The per-mill symbol. For example, when parsing the previous
+%\meta{numeric block}, this command can be redefined to the
+%\marg{per-mill sym} element.
+%
+%\begin{definition}
+%\cs{texosquerypatfmticurrencysign}
+%\end{definition}
+%The international currency sign. This is defined as \texttt{¤} by
+%default, unless the command \cs{textcurrency} has been defined, in
+%which case that's used instead. If the UTF-8 character ¤ is
+%available and \cs{textcurrency} hasn't been defined before
+%\sty{texosquery} was input, then you will need to redefine this
+%command as appropriate.
+%
+%\begin{definition}
+%\cs{texosquerypatfmtminus}
+%\end{definition}
+%The minus sign.
+%
+%\begin{definition}
+%\cs{texosquerypatfmtplus}
+%\end{definition}
+%The plus sign.
+%
+%\end{description}
+%
+%\section{texosquery.tex: generic \TeX\ code}
+%\label{sec:tex}
+%You can run \app{texosquery} directly from \TeX's shell escape.
+%For example:
+%\begin{verbatim}
+%\input|"texosquery --locale"
+%\end{verbatim}
+%However, \app{texosquery} uses markup commands in some of
+%the results which need to be defined first. The file
+%\file{texosquery.tex} provides generic \TeX\ code to do
+%this for you and stores the result in a control sequence.
+%
+%Plain \TeX\ users can input this file through the usual \cs{input}
+%method:
+%\begin{verbatim}
+%\input texosquery
+%\end{verbatim}
+%\LaTeX\ users may also simply input this file:
+%\begin{verbatim}
+%\input{texosquery}
+%\end{verbatim}
+%but may prefer the standard package approach:
+%\begin{verbatim}
+%\usepackage{texosquery}
+%\end{verbatim}
+%
+%\begin{important}
+%The commands described below are all fragile.
+%\end{important}
+%
+%The basic command to run \app{texosquery} and capture its output
+%in a control sequence is:
+%\begin{definition}
+%\ics{TeXOSQuery}\marg{cs}\marg{args}
+%\end{definition}
+%where \meta{cs} is the control sequence in which to store the
+%result and \meta{args} are the command line arguments to pass to
+%\app{texosquery}. This first locally changes the category code
+%of some problematic characters and defines the short markup
+%commands that \app{texosquery} uses to identify characters that
+%need to be interpreted literally (for example, in file names).
+%These commands will automatically be expanded by \cs{TeXOSQuery}
+%when the result is input. For example
+%\begin{verbatim}
+%texosquery -n
+%\end{verbatim}
+%produces
+%\begin{verbatim}
+%\pdfd \fcln 20161129221559\fpls 00\fapo 00\fapo
+%\end{verbatim}
+%but when used with
+%\begin{verbatim}
+%\TeXOSQuery{\result}{texosquery -n}
+%\end{verbatim}
+%the \cs{result} command will be set to
+%\begin{verbatim}
+%D:20161129221559+00'00'
+%\end{verbatim}
+%where the characters \texttt{D} \text{:} \texttt{+} and
+%\texttt{\textquotesingle} all have category code 12 (other).
+%
+%If you're not able to use \TeX's piped shell escape but you can
+%run \app{texosquery} outside of \TeX, then another approach is to
+%first run \app{texosquery} with the output redirected to a
+%temporary file and then build your document, but instead of using
+%\cs{TeXOSQuery}, use:
+%\begin{definition}
+%\ics{TeXOSQueryFromFile}\marg{cs}\marg{file name}
+%\end{definition}
+%where \meta{file name} is the name of the temporary file. For
+%example, first run
+%\begin{verbatim}
+%texosquery -b -n > tmpresult.tex
+%\end{verbatim}
+%and then in the document:
+%\begin{verbatim}
+%\TeXOSQueryFromFile{\result}{tmpresult}
+%\end{verbatim}
+%Avoid spaces and other awkward characters in the file name.
+%
+%In the case of \cs{TeXOSQuery}, if the command failed, \meta{cs} will be set
+%to empty. This will also occur with \cs{TeXOSQueryFromFile} if the
+%file doesn't exist. It's best to always test for success after use.
+%For example:
+%\begin{verbatim}
+%\TeXOSQuery{\result}{-b}
+%\ifx\result\empty
+% Failed!
+%\else
+% Result: \result.
+%\fi
+%\end{verbatim}
+%
+%In the case of \cs{TeXOSQuery}, failure can occur because the dry run mode
+%was on, or it can occur
+%if the query was denied (for example, forbidden file access),
+%or if there's a syntax error in the system call. In the case of
+%\cs{TeXOSQueryFromFile}, the control sequence will be empty if the
+%file doesn't exist or if the file was empty.
+%
+%As from
+%version 1.2, \app{texosquery} checks the \texttt{openin\_any}
+%setting, which may forbid read access. Java's security manager
+%or the filing system may also forbid read access.
+%
+%To determine the cause of the error, first inspect the log file to
+%check if the shell escape was used. In the above example, if
+%the shell escape was permitted, then the log file should include
+%\begin{verbatim}
+%(|texosquery -b)
+%\end{verbatim}
+%Copy and paste the system call (\verb|texosquery -b| in
+%the above case) into your
+%\href{http://www.dickimaw-books.com/latex/novices/html/terminal.html}{command
+%prompt or terminal} and insert
+%the \longarg{debug} switch at the start of the argument list. For
+%example:
+%\begin{verbatim}
+%texosquery --debug -b
+%\end{verbatim}
+%This should help determine whether it's a syntax error or a query forbidden
+%by the operating system.
+%
+%If multiple queries are required, it's more efficient to
+%perform them all in one go. For example:
+%\begin{verbatim}
+%\TeXOSQuery{\result}{-l -n -o}
+%
+%\def\parseresult#1#2#3{%
+% Locale: #1. Now: #2. OS: #3.%
+%}
+%
+%\ifx\result\empty
+% Query failed.
+%\else
+% \expandafter\parseresult\result
+%\fi
+%\end{verbatim}
+%(Make sure you have at least v1.1 for this to work correctly.)
+%
+%\begin{important}
+%Take care of characters that have a special meaning to your shell.
+%For example, bash interprets \verb|#| as a comment. For example, if
+%you have a file called \texttt{image\#1.png}, then you can't simply
+%do
+%\begin{verbatim}
+%\TeXOSQuery{\result}{-p image#1.png}
+%\end{verbatim}
+%since bash will pass this as
+%\begin{verbatim}
+%texosquery -p image
+%\end{verbatim}
+%(The \verb|#1.png| part is treated as a comment.) Nor can you do
+%\begin{verbatim}
+%\TeXOSQuery{\result}{-p image\#1.png}
+%\end{verbatim}
+%as \TeX\ will replace the \verb|\#| with \verb|#| when passing the
+%command invocation to the shell. The only way to deal with this
+%situation is to do
+%\begin{verbatim}
+%\TeXOSQuery{\result}{-p image\string\#1.png}
+%\end{verbatim}
+%to protect the \verb|#| character from both \TeX\ and the shell.
+%\end{important}
+%
+%Dry run mode is determined by the conditional
+%\begin{definition}
+%\ics{ifTeXOSQueryDryRun}
+%\end{definition}
+%If true, the shell escape won't be used and the requested command
+%invocation will be printed in the transcript file prefixed with
+%\begin{verbatim}
+%TeXOSQuery:
+%\end{verbatim}
+%(the control sequence \meta{cs} will be set to empty).
+%
+%\begin{important}
+%Remember that a query can still fail even if the dry run mode is
+%off.
+%\end{important}
+%
+%Note that if you switch off the dry run mode when the
+%shell escape setting forbids the execution of \app{texosquery},
+%then you'll get the rather annoying error:
+%\begin{verbatim}
+%runpopen command not allowed: texosquery
+%
+%! I can't find file `"|texosquery -b"'.
+%\TeXOSQueryInvoker ...TeXOSInvokerName \space #1"
+%
+%\TeXOSQuery ...noexpand #1{\TeXOSQueryInvoker {#2}
+% }}\x \fi
+%l.5 \TeXOSQuery{\result}{-b}
+% ^^M
+%(Press Enter to retry, or Control-D to exit)
+%\end{verbatim}
+%By default, the dry run mode is only switched off if the
+%unrestricted shell escape mode is on (detected through
+%\cs{shellescape} or \cs{pdfshellescape}).
+%
+%If \app{texosquery} is added to the restricted list, you can add
+%\begin{verbatim}
+%\TeXOSQueryAllowRestricted
+%\end{verbatim}
+%to the \file{texosquery.cfg} file. (This command can't be used
+%outside of that file.)
+%
+%If you get the above error, then:
+%\begin{itemize}
+%\item make sure you don't have
+%\cs{TeXOSQueryAllowRestricted} in your \file{texosquery.cfg}
+%file;
+%\item make sure you run \TeX\ with the shell escape enabled;
+%\item check the definition of \cs{TeXOSInvokerName};
+%\item try using the application directly from the command prompt or
+%terminal. For example, in the above message, the bit between
+%\texttt{\textasciigrave\textquotedbl\textbar} and
+%\texttt{\textquotedbl\textquotesingle} (that is, \texttt{texosquery-jre8 -b})
+%shows the attempted system call. Copy and paste it directly
+%into your operating system's
+%\href{http://www.dickimaw-books.com/latex/novices/html/terminal.html}{command
+%prompt or terminal} and to check the application has been installed
+%correctly.
+%\end{itemize}
+%
+%The \cs{TeXOSQuery} command uses \ics{TeXOSInvokerName} to reference the
+%application name. This defaults to \app{texosquery} but needs to be
+%redefined to reflect the particular system call that's required.
+%For example, \texttt{texosquery} (Java~7) or
+%\texttt{texosquery-jre8} (Java~8). This redefinition
+%can be done in the configuration file \file{texosquery.cfg} for a
+%system-wide setting. See section~\ref{sec:setup} for further
+%details.
+%
+%\begin{important}
+%Some of the shortcut commands listed below require extra arguments after the
+%relevant switch. These are automatically enclosed in single-quotes
+%to protect any spaces, but only with the \emph{unrestricted} shell escape.
+%If the argument actually contains any single-quote
+%characters, make sure you use \verb|\string\'| to prevent
+%interference. The quote character is forbidden in the shell escape
+%in restricted mode (see section~\ref{sec:restricted}), so the
+%arguments won't be delimited in this case and the quote character
+%should be avoided.
+%
+%Since a file name reference may need to be obtained from \cs{jobname},
+%which sometimes includes double-quotes, the first double-quote pair
+%found is stripped in file name arguments. Any other double-quotes
+%will need to be protected in the same manner as single-quotes
+%(but this shouldn't be an issue if you use a safe file naming scheme)
+%and again be aware of the limitations imposed by the restricted
+%shell escape.
+%
+%All paths should use a forward slash for the directory divider.
+%\end{important}
+%
+%\subsection{Locale}
+%The locale (\shortarg{l} or \longarg{locale-lcs}) information can be
+%obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryLocale}\marg{cs}
+%\end{definition}
+%Note that this uses the lower case codeset form, which has a better
+%chance of matching the encoding names used by the \sty{inputenc}
+%package. If you want the unprocessed codeset name, you can do:
+%\begin{definition}
+%\cs{TeXOSQuery}\marg{cs}\{\shortarg{L}\}
+%\end{definition}
+%If you just want the codeset in the same form as
+%\longarg{locale-lcs} you can do:
+%\begin{definition}
+%\cs{TeXOSQuery}\marg{cs}\{\shortarg{C}\}
+%\end{definition}
+%Similarly for the unprocessed codeset (\longarg{encoding}).
+%
+%The \gls{IETF} \gls{BCP}~47 language tag (\shortarg{b} or \longarg{bcp47})
+%can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryLangTag}\marg{cs}
+%\end{definition}
+%
+%The numeric separators and currency symbols (\shortarg{N} or
+%\longarg{numeric}) can be obtained using
+%\begin{definition}
+%\ics{TeXOSQueryNumeric}\marg{cs}\marg{locale}
+%\end{definition}
+%The \meta{locale} should be a valid language tag or may be empty
+%for the system's default locale. Similarly for the command below.
+%
+%All the locale data (\shortarg{D} or \longarg{locale-data}) can be
+%obtained using
+%\begin{definition}
+%\ics{TeXOSQueryLocaleData}\marg{cs}\marg{locale}
+%\end{definition}
+%
+%\subsection{Operating System Information}
+%The OS name (\shortarg{o} or \longarg{osname}) can
+%be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryName}\marg{cs}
+%\end{definition}
+%
+%The OS version (\shortarg{r} or \longarg{osversion}) can
+%be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryVersion}\marg{cs}
+%\end{definition}
+%
+%The OS architecture (\shortarg{a} or \longarg{osarch}) can
+%be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryArch}\marg{cs}
+%\end{definition}
+%
+%\subsection{Dates and Times}
+%The current date and time information (\shortarg{M} or
+%\longarg{date-time}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryDateTime}\marg{cs}
+%\end{definition}
+%Example usage:
+%\begin{verbatim}
+%\texosquerydefpattern{\pattern}{\%2d/\%2M/\%4y \%2H:\%2m:\%2s}
+%
+%\TeXOSQueryDateTime{\datetimedata}
+%
+%\ifx\datetimedata\empty
+% Query Failed!
+%\else
+% \expandafter\texosqueryfmtdatetime\expandafter\pattern\datetimedata
+%\fi
+%\end{verbatim}
+%Note that commands such as \cs{texosqueryfmtpatMMM} will need to be
+%defined to produce textual elements. See
+%sections~\ref{sec:patternformats} and \ref{sec:applydatetimepatterns} for
+%further details.
+%
+%The time zone mappings (\shortarg{Z} or
+%\longarg{time-zones}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryTimeZones}\marg{cs}\marg{locale}
+%\end{definition}
+%Leave \meta{locale} empty if the default locale is required.
+%
+%The current date-time stamp in PDF format (\shortarg{n} or
+%\longarg{pdfnow}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryNow}\marg{cs}
+%\end{definition}
+%This is provided for the benefit of users who don't have
+%\cs{pdfcreationdate} defined by their \TeX\ format (for example,
+%\XeTeX).
+%
+%The modification date-time stamp in PDF format for a file
+%(\shortarg{d} or \longarg{pdfdate}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryFileDate}\marg{cs}\marg{filename}
+%\end{definition}
+%where \meta{filename} is the name of the file.
+%This is provided for the benefit of users who don't have
+%\cs{pdffilemoddate} defined by their \TeX\ format.
+%
+%\subsection{File Operations}
+%The current working directory (\shortarg{c} or \longarg{cwd}) can
+%be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryCwd}\marg{cs}
+%\end{definition}
+%
+%The home directory (\shortarg{m} or \longarg{userhome}) can
+%be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryHome}\marg{cs}
+%\end{definition}
+%
+%The temporary directory (\shortarg{t} or \longarg{tmpdir}) can
+%be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryTmpDir}\marg{cs}
+%\end{definition}
+%
+%The size in bytes of a file
+%(\shortarg{s} or \longarg{filesize}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryFileSize}\marg{cs}\marg{filename}
+%\end{definition}
+%where \meta{filename} is the name of the file.
+%This is provided for the benefit of users who don't have
+%\cs{pdffilesize} defined by their \TeX\ format.
+%
+%The URI of a file
+%(\shortarg{u} or \longarg{uri}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryFileURI}\marg{cs}\marg{filename}
+%\end{definition}
+%where \meta{filename} is the name of the file.
+%(Any percent symbols \verb|%| contained in the URI will have
+%their category code set to 12.)
+%
+%The canonical path of a file
+%(\shortarg{p} or \longarg{path}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryFilePath}\marg{cs}\marg{filename}
+%\end{definition}
+%where \meta{filename} is the name of the file.
+%
+%The canonical path of a file's parent
+%(\shortarg{e} or \longarg{dirname}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryDirName}\marg{cs}\marg{filename}
+%\end{definition}
+%where \meta{filename} is the name of the file.
+%
+%The list of files in a given directory
+%(\shortarg{i} or \longarg{list}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryFileList}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%where \meta{sep} is the separator and \meta{dir} is the directory name.
+%For example:
+%\begin{verbatim}
+%\TeXOSQueryFileList{\result}{,}{.}
+%\end{verbatim}
+%will store a comma-separated list of all the files contained in the
+%current directory in the control sequence \cs{result}.
+%
+%To omit directories (\shortarg{ir} or \longarg{list-regular}):
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileList}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%To omit regular files (\shortarg{id} or \longarg{list-dir}):
+%\begin{definition}
+%\ics{TeXOSQuerySubDirList}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%
+%A filtered list of files in a given directory
+%(\shortarg{f} or \longarg{filterlist}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileList}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%where \meta{regex} is a regular expression. \emph{Take care of any backslashes
+%in the regular expression!}
+%For example, to list only those files that have an extension:
+%\begin{verbatim}
+%\TeXOSQueryFilterFileList{\result}{,}{.+\string\..*}{.}
+%\end{verbatim}
+%Note the use of \verb|\string\.| to ensure that \cs{.} isn't
+%interpreted as a command. Another example, list only \file{.png}
+%and \file{.jpg} files in the directory called \file{images}:
+%\begin{verbatim}
+%\TeXOSQueryFilterFileList{\result}{,}{.+\string\.(jpg|png)}{images}
+%\end{verbatim}
+%
+%\begin{important}
+%Unlike most of the return values the \meta{sep} part here isn't
+%escaped, so take care if \meta{sep} contains any commands. For
+%example, if you want to use \verb|\\| as the separator, you'll need
+%to use \verb|\string\noexpand\string\\| in the \meta{sep} part.
+%\begin{verbatim}
+%\TeXOSQueryFilterFileList{\result}{\string\noexpand\string\\}{.*\string\.tex}{.}
+%\end{verbatim}
+%\end{important}
+%
+%If you want the list sorted, you can use the following which set
+%the optional \meta{sort} argument.
+%
+%Order by last modified date starting with the oldest (\texttt{date-ascending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListDateAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListDateAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListDateAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListDateAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or for the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListDateAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or for the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListDateAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%Order by last modified date starting with the newest (\texttt{date-descending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListDateDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListDateDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListDateDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListDateDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListDateDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListDateDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%Order by file size starting with the smallest (\texttt{size-ascending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListSizeAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListSizeAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListSizeAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListSizeAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListSizeAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListSizeAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%Order by file size starting with the largest (\texttt{size-descending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListSizeDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListSizeDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListSizeDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListSizeDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListSizeDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListSizeDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%
+%Order by file name in alphabetical order (\texttt{name-ascending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListNameAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListNameAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListNameAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListNameAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListNameAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListNameAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%Order by file name in reverse alphabetical order (\texttt{name-descending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListNameDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListNameDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListNameDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListNameDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListNameDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListNameDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%
+%Order by file name in case-insensitive alphabetical order
+%(\texttt{iname-ascending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListNameIgnoreCaseAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListNameIgnoreCaseAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListNameIgnoreCaseAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListNameIgnoreCaseAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListNameIgnoreCaseAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListNameIgnoreCaseAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%Order by file name in reverse case-insensitive alphabetical order
+%(\texttt{iname-descending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListNameIgnoreCaseDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListNameIgnoreCaseDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListNameIgnoreCaseDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListNameIgnoreCaseDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListNameIgnoreCaseDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListNameIgnoreCaseDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%Order by file extension in alphabetical order (\texttt{ext-ascending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListExtAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListExtAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListExtAsc}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListExtAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListExtAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListExtAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%Order by file extension in reverse alphabetical order (\texttt{ext-descending}):
+%\begin{definition}
+%\ics{TeXOSQueryFileListExtDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryRegularFileListExtDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or the sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQuerySubDirListExtDes}\marg{cs}\marg{sep}\marg{dir}
+%\end{definition}
+%or for the filtered list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterFileListExtDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered regular files only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterRegularFileListExtDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or the filtered sub-directories only list:
+%\begin{definition}
+%\ics{TeXOSQueryFilterSubDirListExtDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%A recursive filtered list of regular files starting from a given directory
+%on the current working path
+%(\shortarg{w} or \longarg{alk}) can be obtained using:
+%\begin{definition}
+%\ics{TeXOSQueryWalk}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%where \meta{regex} is as for the filtered listings described above.
+%
+%To sort according to last modified date:
+%\begin{definition}
+%\ics{TeXOSQueryWalkDateAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or in reverse order:
+%\begin{definition}
+%\ics{TeXOSQueryWalkDateDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%To sort according to file size:
+%\begin{definition}
+%\ics{TeXOSQueryWalkSizeAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or in reverse order:
+%\begin{definition}
+%\ics{TeXOSQueryWalkSizeDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%To sort according to path name (case-sensitive):
+%\begin{definition}
+%\ics{TeXOSQueryWalkNameAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or in reverse order:
+%\begin{definition}
+%\ics{TeXOSQueryWalkNameDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%To sort according to path name (case-insensitive):
+%\begin{definition}
+%\ics{TeXOSQueryWalkNameIgnoreCaseAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or in reverse order:
+%\begin{definition}
+%\ics{TeXOSQueryWalkNameIgnoreCaseDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%To sort according to file extension:
+%\begin{definition}
+%\ics{TeXOSQueryWalkExtAsc}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%or in reverse order:
+%\begin{definition}
+%\ics{TeXOSQueryWalkExtDes}\marg{cs}\marg{sep}\marg{regex}\marg{dir}
+%\end{definition}
+%
+%\setcounter{secnumdepth}{3}
+%\StopEventually{\clearpage\printabbreviations
+%\clearpage\phantomsection
+%\addcontentsline{toc}{section}{Change History}%
+%\PrintChanges
+%\PrintIndex
+%}
+%
+%\section{The Code}
+%\changes{1.0}{2016-07-08}{Initial release}
+%\subsection{Generic \TeX\ Code}
+%\iffalse
+% \begin{macrocode}
+%<*texosquery.tex>
+% \end{macrocode}
+%\fi
+%Change category code of \texttt{@} if necessary.
+% \begin{macrocode}
+\ifnum\catcode`\@=11\relax
+ \def\@texosquery@restore@at{}%
+\else
+ \expandafter\edef\csname @texosquery@restore@at\endcsname{%
+ \noexpand\catcode`\noexpand\@=\number\catcode`\@\relax
+ }%
+ \catcode`\@=11\relax
+\fi
+% \end{macrocode}
+% Check if already loaded.
+% \begin{macrocode}
+\ifx\TeXOSQuery\undefined \else
+ \@texosquery@restore@at
+ \expandafter\endinput
+\fi
+% \end{macrocode}
+% Version info.
+% \begin{macrocode}
+\expandafter\def\csname ver@texosquery.tex\endcsname{2017/06/20 v1.6 (NLCT)}
+% \end{macrocode}
+%
+%\begin{macro}{\@texosquery@warn}
+%\changes{1.2}{2017-03-23}{added check for tracklang's warning command}
+%Generate warning message. Use \sty{tracklang}'s warning if
+%available (so that the warnings can be disabled for both packages
+%at the same time).
+% \begin{macrocode}
+\ifx\@tracklang@pkgwarn\undefined
+ \ifx\PackageWarning\undefined
+ \def\@texosquery@warn#1{%
+ {%
+ \newlinechar=`\^^J
+ \def\MessageBreak{^^J}%
+ \message{^^Jtexosquery Warning: #1 on line \the\inputlineno.^^J}%
+ }%
+ }
+ \else
+ \def\@texosquery@warn#1{%
+ \PackageWarning{texosquery}{#1}%
+ }
+ \fi
+\else
+ \def\@texosquery@warn#1{%
+ \@tracklang@pkgwarn{texosquery}{#1}%
+ }
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@info}
+%\changes{1.4}{??}{new}
+% \begin{macrocode}
+\ifx\PackageInfo\undefined
+ \def\@texosquery@info#1{%
+ {%
+ \newlinechar=`\^^J
+ \def\MessageBreak{^^J}%
+ \message{^^Jtexosquery Info: #1^^J}%
+ }%
+ }
+\else
+ \def\@texosquery@info#1{%
+ \PackageInfo{texosquery}{#1}%
+ }
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@err}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\PackageError\undefined
+ \def\@texosquery@err#1#2{%
+ \errhelp{#2}%
+ \errmessage{texosquery: #1}}
+\else
+ \def\@texosquery@err#1#2{\PackageError{texosquery}{#1}{#2}}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@ifundef}
+%This is defined in the same way as \sty{tracklang}'s
+%\cs{@tracklang@ifundef}. (Can't assume \sty{tracklang} has been
+%loaded.)
+% \begin{macrocode}
+\long\def\@texosquery@ifundef#1#2#3{%
+ \ifcsname#1\endcsname
+ \expandafter\ifx\csname #1\endcsname\relax
+ #2%
+ \else
+ #3%
+ \fi
+ \else
+ \expandafter\ifx\csname #1\endcsname\relax
+ #2%
+ \else
+ #3%
+ \fi
+ \fi
+}
+\ifx\ifcsname\undefined
+ \long\def\@texosquery@ifundef#1#2#3{%
+ \expandafter\ifx\csname #1\endcsname\relax
+ #2%
+ \else
+ #3%
+ \fi
+ }
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSInvokerName}
+%The name of the \app{texosquery} application.
+% \begin{macrocode}
+\def\TeXOSInvokerName{texosquery}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquery@input}
+%\changes{1.4}{2017-05-05}{new}
+%If we're using \LaTeX, we'll need to use \cs{@@input} rather
+%than \cs{input}.
+% \begin{macrocode}
+\ifx\@@input\undefined
+ \let\texosquery@input\input
+\else
+ \let\texosquery@input\@@input
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryInvoker}
+%Invoke the application in the piped shell escape with the given
+%arguments.
+% \begin{macrocode}
+\def\TeXOSQueryInvoker#1{\texosquery@input|"\TeXOSInvokerName\space#1" }
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\ifTeXOSQueryDryRun}
+%Provide a dry-run mode.
+%\changes{1.1}{2016-07-14}{dry run mode only false by default if
+%used in unrestricted mode}
+% \begin{macrocode}
+\newif\ifTeXOSQueryDryRun \TeXOSQueryDryRuntrue
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryAllowRestricted}
+%\changes{1.2}{2017-03-23}{new}
+%The default behaviour only switches off the dry-run mode if the
+%shell escape is unrestricted. The configuration file may override
+%this with \cs{TeXOSQueryAllowRestricted}, which will allow
+%the dry run mode to be switched off if restricted mode is detected.
+% \begin{macrocode}
+\def\TeXOSQueryAllowRestricted{%
+ \def\@texosquery@allowrestricted##1##2{##1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryDenyRestricted}
+%\changes{1.2}{2017-03-23}{new}
+%Switch it off.
+% \begin{macrocode}
+\def\TeXOSQueryDenyRestricted{%
+ \def\@texosquery@allowrestricted##1##2{##2}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@allowrestricted}
+%\changes{1.2}{2017-03-23}{new}
+%Initialise to prevent shell escape in restricted mode.
+% \begin{macrocode}
+\def\@texosquery@allowrestricted#1#2{#2}%
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSInvokerRestrictedMessage}
+%\changes{1.4}{2017-05-05}{new}
+%Message used if \TeX\ is run in restricted mode but the invoker
+%name hasn't been allowed. This may be redefined in the
+%configuration file to customise the message if the invoker name
+%isn't allowed. Set to empty or \cs{relax} if the message should be
+%omitted.
+% \begin{macrocode}
+\def\TeXOSInvokerRestrictedMessage{%
+ \string\TeXOSQuery\space doesn't work in dry run mode.
+ \MessageBreak
+ If `\TeXOSInvokerName' has been added to the \MessageBreak
+ restricted list, make sure that
+ \MessageBreak \string\TeXOSQueryAllowRestricted\space
+ \MessageBreak has been uncommented in the configuration file
+ \MessageBreak (texosquery.cfg) otherwise you need to use the
+ \MessageBreak unrestricted shell escape}
+% \end{macrocode}
+%\end{macro}
+%Only need to write this information to the transcript if
+%\cs{TeXOSQuery} is actually used and the dry run mode is on in
+%restricted mode.
+%\begin{macro}{\@texosquery@dorestrictedmessage}
+% \begin{macrocode}
+\let\@texosquery@dorestrictedmessage\relax
+% \end{macrocode}
+%\end{macro}
+%
+%Load the configuration file if it exists.
+%\changes{1.2}{2017-03-23}{added texosquery.cfg}
+% \begin{macrocode}
+\openin0=texosquery.cfg \ifeof0\relax \else
+ \closein0\relax
+ \begingroup
+ \@texosquery@info{reading configuration file}%
+ \endgroup
+ \input texosquery.cfg
+\fi
+% \end{macrocode}
+%Disable cfg-only commands:
+% \begin{macrocode}
+\def\TeXOSQueryAllowRestricted{%
+ \@texosquery@warn{\string\TeXOSQueryAllowRestricted\space
+ ignored (only allowed in texosquery.cfg)}%
+}
+\def\TeXOSQueryDenyRestricted{%
+ \@texosquery@warn{\string\TeXOSQueryDenyRestricted\space
+ ignored (only allowed in texosquery.cfg)}%
+}
+% \end{macrocode}
+%
+%\begin{macro}{\texosquery@shellescape}
+%\changes{1.4}{2017-05-05}{new}
+%We need to know the shell escape mode. This is provided by
+%PDF\TeX's \cs{pdfshellescape} primitive or \XeTeX's
+%\cs{shellescape} primitive. It's a little more complicated in
+%Lua\TeX. Initialise to 0:
+% \begin{macrocode}
+\chardef\texosquery@shellescape=0
+% \end{macrocode}
+% Test for the PDF\TeX\ and \XeTeX\ primitives.
+% \begin{macrocode}
+\ifx\shellescape\undefined
+ \ifx\pdfshellescape\undefined
+% \end{macrocode}
+%Neither primitive defined. Has \sty{pdftexcmds} been loaded? If so,
+%we can use \cs{pdf@shellescape}.
+% \begin{macrocode}
+ \ifx\pdf@shellescape\undefined
+% \end{macrocode}
+%\cs{pdf@shellescape} hasn't been defined. Is Lua\TeX\ in use?
+% \begin{macrocode}
+ \ifx\directlua\undefined
+% \end{macrocode}
+%Not Lua\TeX. Most likely an old \TeX\ format. Have to assume the
+%shell escape is unavailable.
+% \begin{macrocode}
+ \else
+% \end{macrocode}
+%Use Lua to find the shell escape. This uses the same method as
+%\sty{pdftexcmds}. (Not loading that package, as this is the only
+%thing required from it.)
+% \begin{macrocode}
+ \edef\texosquery@shellescape{\directlua0{
+ if os.execute then
+ if status
+ and status.luatex_version
+ and status.luatex_version >= 68 then
+ tex.write(os.execute())
+ else
+ local result = os.execute()
+ if result == 0 then
+ tex.write("0")
+ else
+ if result == nil then
+ tex.write("0")
+ else
+ tex.write("1")
+ end
+ end
+ end
+ else
+ tex.write("0")
+ end
+ }}
+ \fi
+ \else
+ \let\texosquery@shellescape\pdf@shellescape
+ \fi
+ \else
+% \end{macrocode}
+%PDF\TeX's \cs{pdfshellescape} has been defined, so that can be
+%used, but first check it hasn't been set to \cs{relax}.
+% \begin{macrocode}
+ \ifx\pdfshellescape\relax
+% \end{macrocode}
+%Something's meddled with \cs{pdfshellescape}, so try
+%\cs{pdf@shellescape}.
+% \begin{macrocode}
+ \ifx\pdf@shellescape\undefined
+% \end{macrocode}
+%\cs{pdf@shellescape} hasn't been defined. Is Lua\TeX\ in use?
+% \begin{macrocode}
+ \ifx\directlua\undefined
+% \end{macrocode}
+%Not Lua\TeX. Most likely an old \TeX\ format. Have to assume the
+%shell escape is unavailable.
+% \begin{macrocode}
+ \else
+% \end{macrocode}
+%Use Lua to find the shell escape.
+% \begin{macrocode}
+ \edef\texosquery@shellescape{\directlua0{
+ if os.execute then
+ if status
+ and status.luatex_version
+ and status.luatex_version >= 68 then
+ tex.write(os.execute())
+ else
+ local result = os.execute()
+ if result == 0 then
+ tex.write("0")
+ else
+ if result == nil then
+ tex.write("0")
+ else
+ tex.write("1")
+ end
+ end
+ end
+ else
+ tex.write("0")
+ end
+ }}
+ \fi
+ \else
+ \let\texosquery@shellescape\pdf@shellescape
+ \fi
+ \else
+ \let\texosquery@shellescape\pdfshellescape
+ \fi
+ \fi
+\else
+% \end{macrocode}
+%\XeTeX's \cs{shellescape} has been defined, so that can be used,
+%but first check it hasn't been set to \cs{relax}.
+% \begin{macrocode}
+ \ifx\shellescape\relax
+% \end{macrocode}
+%Something's meddled with \cs{shellescape}, so try
+%\cs{pdfshellescape}.
+% \begin{macrocode}
+ \ifx\pdfshellescape\undefined
+ \else
+% \end{macrocode}
+%\cs{pdfshellescape} has been defined, so that can be used, but
+%first check it hasn't been set to \cs{relax}.
+% \begin{macrocode}
+ \ifx\pdfshellescape\relax
+% \end{macrocode}
+%Something's meddled with \cs{pdfshellescape}, so try
+%\cs{pdf@shellescape}.
+% \begin{macrocode}
+ \ifx\pdf@shellescape\undefined
+% \end{macrocode}
+%Try Lua.
+% \begin{macrocode}
+ \ifx\directlua\undefined
+% \end{macrocode}
+%Not Lua\TeX. Most likely an old \TeX\ format. Have to assume the
+%shell escape is unavailable.
+% \begin{macrocode}
+ \else
+% \end{macrocode}
+%Use Lua to find the shell escape.
+% \begin{macrocode}
+ \edef\texosquery@shellescape{\directlua0{
+ if os.execute then
+ if status
+ and status.luatex_version
+ and status.luatex_version >= 68 then
+ tex.write(os.execute())
+ else
+ local result = os.execute()
+ if result == 0 then
+ tex.write("0")
+ else
+ if result == nil then
+ tex.write("0")
+ else
+ tex.write("1")
+ end
+ end
+ end
+ else
+ tex.write("0")
+ end
+ }}
+ \fi
+ \else
+ \let\texosquery@shellescape\pdf@shellescape
+ \fi
+ \else
+ \let\texosquery@shellescape\pdfshellescape
+ \fi
+ \fi
+ \else
+ \let\texosquery@shellescape\shellescape
+ \fi
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%If shell escape is unrestricted, automatically switch off dry-run
+%mode, unless the cfg file has allowed it.
+% \begin{macrocode}
+\ifcase\texosquery@shellescape
+ \@texosquery@info{shell escape disabled, dry-run mode on}
+\or
+ \TeXOSQueryDryRunfalse
+\or
+ \@texosquery@allowrestricted
+ {%
+ \TeXOSQueryDryRunfalse
+ }
+ {%
+ \@texosquery@info{shell escape restricted, dry-run mode on.}
+% \end{macrocode}
+%Enable the restricted warning message, but it only needs to be
+%written once, so disable it after use.
+% \begin{macrocode}
+ \def\@texosquery@dorestrictedmessage{%
+ \ifx\TeXOSInvokerRestrictedMessage\empty
+ \else
+ \ifx\TeXOSInvokerRestrictedMessage\relax
+ \else
+ \@texosquery@warn{\TeXOSInvokerRestrictedMessage}%
+ \fi
+ \fi
+ \let\@texosquery@dorestrictedmessage\relax
+ }
+ }
+\fi
+% \end{macrocode}
+%
+%\begin{macro}{\@texosquery@edef}
+%Need to provide some protection (if available) against non-ASCII characters
+%that have been made active by \sty{inputenc} when reading in the
+%results of the shell escape. This command may be defined before loading
+%\sty{texosquery}, otherwise it's set to \cs{protected@edef}, if
+%defined, or \cs{edef}.
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\@texosquery@edef\undefined
+ \ifx\protected@edef\undefined
+ \let\@texosquery@edef\edef
+ \else
+ \let\@texosquery@edef\protected@edef
+ \fi
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%Provide some utility commands. (Can't use \cs{@gobble} etc, as we
+%may not be using \LaTeX.)
+%\begin{macro}{\@texosquery@gobble}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@gobble#1{}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\@texosquery@firstofone}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@firstofone#1{#1}
+% \end{macrocode}
+%\end{macro}
+%
+%The results obtained from \app{texosquery} may be file names for
+%use in commands like \cs{input} or \cs{includegraphics} or they may
+%be text that needs typesetting (such as month names) or they may be
+%date-time patterns or numeric patterns or they may be PDF date-time
+%strings, which may need to have the category code of the initial
+%\qt{D} set to 12 for parsing commands that include this character
+%in the argument syntax.
+%
+%This means that we need to take special characters into account,
+%but the way they are dealt with vary according to context. For
+%example, \verb|#| needs to have the category code set to 12 if it's
+%part of a file name. If an image file is called, say,
+%\verb|test_imagefile#.png| then the following doesn't work:
+%\begin{verbatim}
+%\includegraphics{test\_imagefile\#}
+%\end{verbatim}
+%It needs to be
+%\begin{verbatim}
+%\includegraphics{test\string_imagefile\string#}
+%\end{verbatim}
+%or
+%\begin{verbatim}
+%\includegraphics{\detokenize{test_imagefile#}}
+%\end{verbatim}
+%The first two versions of \sty{texosquery} try to deal with this by
+%simply changing the category code of \verb|_| to 12 and getting
+%\app{texosquery} to replace all instances of \verb|#| with
+%\verb|\#|. This hash substitution doesn't work with the above image
+%example so version 1.2 introduced a new command that
+%\app{texosquery} could use instead of \verb|\#| that expands to
+%\verb|\string#|. This now solves the problem for file names that
+%are obtained through \app{texosquery}, but \app{texosquery} doesn't
+%solely return file names. It also returns text that needs
+%typesetting and it also returns numeric patterns, which in their
+%raw form include \verb|#| as a digit identifier.
+%
+%This means that we can't simply detokenize the result from
+%\app{texosquery}. Instead \app{texosquery} replaces problematic
+%characters with control sequences \emph{according to context}.
+%For example, \cs{texosqueryhash} is used in a file name context,
+%\cs{texosquerytexthash} is used in a textual context and
+%\cs{texosquerypatdigitnozero} in a numeric pattern context.
+%
+%These long control sequence names clutter the results when testing
+%the application directly in a terminal, so the Java code uses short
+%forms that are locally defined by \cs{TeXOSQuery} to expand to the
+%longer forms.
+%
+%\begin{macro}{\texosquerynonasciiwrap}
+%\changes{1.2}{2017-03-23}{new}
+%Allow a way to deal with non-ASCII characters returned by
+%\app{texosquery}. \cs{TeXOSQuery} locally defines \cs{twrp}
+%to this command. By default this just does its argument
+%but may be redefined. For example, if the document uses a different
+%file encoding to Java, then this command might need to be redefined
+%to perform the appropriate conversion.
+% \begin{macrocode}
+\def\texosquerynonasciiwrap#1{#1}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerynonasciidetokwrap}
+%\changes{1.2}{2017-03-23}{new}
+%We also need to allow for non-ASCII characters in file names. In
+%this case the argument needs detokenizing. With \eTeX, we can
+%simply use \cs{detokenize} but we need to allow for plain
+%non-extended \TeX, so check for the existence of \cs{detokenize}
+%first.
+% \begin{macrocode}
+\ifx\detokenize\undefined
+% \end{macrocode}
+%This won't work for characters consisting of multiple octets,
+%but if users want UTF-8 support then they really need \eTeX\ at the
+%very least (but ideally \XeTeX\ or \LuaTeX).
+% \begin{macrocode}
+ \def\texosquerynonasciidetokwrap#1{\string#1}
+\else
+ \def\texosquerynonasciidetokwrap#1{\detokenize{#1}}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%Now define commands used in \cs{TeXOSQuery} for various escaped characters.
+%The literal versions are for file names. The textual versions are
+%for use within the document text. For completeness, all the ASCII
+%punctuation characters have both a literal and textual version.
+%This helps to protect against \sty{babel} shorthands etc.
+%
+%\begin{macro}{\texosquerybackslash}
+%\changes{1.2}{2017-03-23}{new}
+%Literal backslash.
+% \begin{macrocode}
+\edef\texosquerybackslash{\expandafter\@texosquery@gobble\string\\}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextbackslash}
+%\changes{1.2}{2017-03-23}{new}
+%Textual backslash.
+% \begin{macrocode}
+\ifx\textbackslash\undefined
+ \def\texosquerytextbackslash{\texosquerybackslash}
+\else
+ \def\texosquerytextbackslash{\noexpand\textbackslash}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryleftbrace}
+%\changes{1.2}{2017-03-23}{new}
+%Literal left brace.
+% \begin{macrocode}
+\edef\texosqueryleftbrace{\expandafter\@texosquery@gobble\string\{}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextleftbrace}
+%\changes{1.2}{2017-03-23}{new}
+%Textual left brace.
+% \begin{macrocode}
+\def\texosquerytextleftbrace{\{}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryrightbrace}
+%\changes{1.2}{2017-03-23}{new}
+%Literal right brace.
+% \begin{macrocode}
+\edef\texosqueryrightbrace{\expandafter\@texosquery@gobble\string\}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextrightbrace}
+%\changes{1.2}{2017-03-23}{new}
+%Textual right brace.
+% \begin{macrocode}
+\def\texosquerytextrightbrace{\}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryhash}
+%\changes{1.2}{2017-03-23}{new}
+%Literal hash.
+% \begin{macrocode}
+\edef\texosqueryhash{\expandafter\@texosquery@gobble\string\#}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytexthash}
+%\changes{1.2}{2017-03-23}{new}
+%Textual hash.
+% \begin{macrocode}
+\def\texosquerytexthash{\#}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryunderscore}
+%\changes{1.2}{2017-03-23}{new}
+%Literal underscore.
+% \begin{macrocode}
+\edef\texosqueryunderscore{\expandafter\@texosquery@gobble\string\_}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextunderscore}
+%\changes{1.2}{2017-03-23}{new}
+%Textual underscore.
+% \begin{macrocode}
+\def\texosquerytextunderscore{\_}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerybacktick}
+%\changes{1.2}{2017-03-23}{new}
+%Literal grave.
+% \begin{macrocode}
+\edef\texosquerybacktick{\string`}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextbacktick}
+%\changes{1.2}{2017-03-23}{new}
+%Textual open quote.
+% \begin{macrocode}
+\def\texosquerytextbacktick{`}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryclosequote}
+%\changes{1.2}{2017-03-23}{new}
+%Literal apostrophe.
+% \begin{macrocode}
+\edef\texosqueryclosequote{\string'}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextclosequote}
+%\changes{1.2}{2017-03-23}{new}
+%Textual apostrophe / single closing quote.
+% \begin{macrocode}
+\def\texosquerytextclosequote{'}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerydoublequote}
+%\changes{1.2}{2017-03-23}{new}
+%Literal double-quote.
+% \begin{macrocode}
+\edef\texosquerydoublequote{\string"}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextdoublequote}
+%\changes{1.2}{2017-03-23}{new}
+%Textual double-quote.
+% \begin{macrocode}
+\def\texosquerytextdoublequote{"}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycolon}
+%\changes{1.2}{2017-03-23}{new}
+%Literal colon.
+% \begin{macrocode}
+\edef\texosquerycolon{\string:}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextcolon}
+%\changes{1.2}{2017-03-23}{new}
+%Textual colon.
+% \begin{macrocode}
+\def\texosquerytextcolon{:}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerysemicolon}
+%\changes{1.2}{2017-03-23}{new}
+%Literal semi-colon.
+% \begin{macrocode}
+\edef\texosquerysemicolon{\string;}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextsemicolon}
+%\changes{1.2}{2017-03-23}{new}
+%Textual semi-colon.
+% \begin{macrocode}
+\def\texosquerytextsemicolon{;}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryequals}
+%\changes{1.2}{2017-03-23}{new}
+%Literal equals.
+% \begin{macrocode}
+\edef\texosqueryequals{\string=}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextequals}
+%\changes{1.2}{2017-03-23}{new}
+%Textual equals.
+% \begin{macrocode}
+\def\texosquerytextequals{=}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryslash}
+%\changes{1.2}{2017-03-23}{new}
+%Literal slash.
+% \begin{macrocode}
+\edef\texosqueryslash{\string/}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextslash}
+%\changes{1.2}{2017-03-23}{new}
+%Textual slash.
+% \begin{macrocode}
+\def\texosquerytextslash{/}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryhyphen}
+%\changes{1.2}{2017-03-23}{new}
+%Literal hyphen.
+% \begin{macrocode}
+\edef\texosqueryhyphen{\string-}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytexthyphen}
+%\changes{1.2}{2017-03-23}{new}
+%Textual hyphen.
+% \begin{macrocode}
+\def\texosquerytexthyphen{-}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryplus}
+%\changes{1.2}{2017-03-23}{new}
+%Literal plus.
+% \begin{macrocode}
+\edef\texosqueryplus{\string+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextplus}
+%\changes{1.2}{2017-03-23}{new}
+%Textual plus.
+% \begin{macrocode}
+\def\texosquerytextplus{+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryperiod}
+%\changes{1.2}{2017-03-23}{new}
+%Literal period.
+% \begin{macrocode}
+\edef\texosqueryperiod{\string.}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextperiod}
+%\changes{1.2}{2017-03-23}{new}
+%Textual period.
+% \begin{macrocode}
+\def\texosquerytextperiod{.}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycomma}
+%\changes{1.2}{2017-03-23}{new}
+%Literal comma.
+% \begin{macrocode}
+\edef\texosquerycomma{\string,}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextcomma}
+%\changes{1.2}{2017-03-23}{new}
+%Textual comma.
+% \begin{macrocode}
+\def\texosquerytextcomma{,}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryopenparen}
+%\changes{1.2}{2017-03-23}{new}
+%Literal open bracket.
+% \begin{macrocode}
+\edef\texosqueryopenparen{\string(}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextopenparen}
+%\changes{1.2}{2017-03-23}{new}
+%Textual open bracket.
+% \begin{macrocode}
+\def\texosquerytextopenparen{(}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycloseparen}
+%\changes{1.2}{2017-03-23}{new}
+%Literal close bracket.
+% \begin{macrocode}
+\edef\texosquerycloseparen{\string)}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextcloseparen}
+%\changes{1.2}{2017-03-23}{new}
+%Textual close bracket.
+% \begin{macrocode}
+\def\texosquerytextcloseparen{)}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryopensq}
+%\changes{1.2}{2017-03-23}{new}
+%Literal open square bracket.
+% \begin{macrocode}
+\edef\texosqueryopensq{\string[}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextopensq}
+%\changes{1.2}{2017-03-23}{new}
+%Textual open square bracket.
+% \begin{macrocode}
+\def\texosquerytextopensq{[}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryclosesq}
+%\changes{1.2}{2017-03-23}{new}
+%Literal close square bracket.
+% \begin{macrocode}
+\edef\texosqueryclosesq{\string]}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextclosesq}
+%\changes{1.2}{2017-03-23}{new}
+%Textual close square bracket.
+% \begin{macrocode}
+\def\texosquerytextclosesq{]}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryasterisk}
+%\changes{1.2}{2017-03-23}{new}
+%Literal asterisk.
+% \begin{macrocode}
+\edef\texosqueryasterisk{\string*}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextasterisk}
+%\changes{1.2}{2017-03-23}{new}
+%Textual asterisk.
+% \begin{macrocode}
+\def\texosquerytextasterisk{*}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryatchar}
+%\changes{1.2}{2017-03-23}{new}
+%Literal at character.
+% \begin{macrocode}
+\edef\texosqueryatchar{\string @}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextatchar}
+%\changes{1.2}{2017-03-23}{new}
+%Textual at character.
+% \begin{macrocode}
+\def\texosquerytextatchar{@}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerybar}
+%\changes{1.2}{2017-03-23}{new}
+%Literal bar.
+% \begin{macrocode}
+\edef\texosquerybar{\string|}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextbar}
+%\changes{1.2}{2017-03-23}{new}
+%Textual bar.
+% \begin{macrocode}
+\ifx\undefined\textbar
+ \def\texosquerytextbar{|}
+\else
+ \def\texosquerytextbar{\ifmmode|\else\textbar\fi}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerylessthan}
+%\changes{1.2}{2017-03-23}{new}
+%Literal less than.
+% \begin{macrocode}
+\edef\texosquerylessthan{\string<}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextlessthan}
+%\changes{1.2}{2017-03-23}{new}
+%Textual less than.
+% \begin{macrocode}
+\ifx\undefined\textless
+ \def\texosquerytextlessthan{<}
+\else
+ \def\texosquerytextlessthan{\ifmmode<\else\textless\fi}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerygreaterthan}
+%\changes{1.2}{2017-03-23}{new}
+%Literal greater than.
+% \begin{macrocode}
+\edef\texosquerygreaterthan{\string>}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextgreaterthan}
+%\changes{1.2}{2017-03-23}{new}
+%Textual greater than.
+% \begin{macrocode}
+\ifx\undefined\textgreater
+ \def\texosquerytextgreaterthan{>}
+\else
+ \def\texosquerytextgreaterthan{\ifmmode<\else\textgreater\fi}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytilde}
+%\changes{1.2}{2017-03-23}{new}
+%Literal tilde.
+% \begin{macrocode}
+\edef\texosquerytilde{\string~}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytexttilde}
+%\changes{1.2}{2017-03-23}{new}
+%Textual tilde.
+% \begin{macrocode}
+\ifx\textasciitilde\undefined
+ \def\texosquerytexttilde{\string~}
+\else
+ \def\texosquerytexttilde{\textasciitilde}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycircum}
+%\changes{1.2}{2017-03-23}{new}
+%Literal circumflex.
+% \begin{macrocode}
+\edef\texosquerycircum{\string^}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextcircum}
+%\changes{1.2}{2017-03-23}{new}
+%Textual circumflex.
+% \begin{macrocode}
+\ifx\textasciicircum\undefined
+ \def\texosquerytextcircum{\string^}
+\else
+ \def\texosquerytextcircum{\textasciicircum}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryampersand}
+%\changes{1.2}{2017-03-23}{new}
+%Literal ampersand.
+% \begin{macrocode}
+\edef\texosqueryampersand{\string&}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextampersand}
+%\changes{1.2}{2017-03-23}{new}
+%Textual ampersand.
+% \begin{macrocode}
+\def\texosquerytextampersand{\&}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerydollar}
+%\changes{1.2}{2017-03-23}{new}
+%Literal dollar. (This could just be defined as \verb|\string$|, but
+%that plays havoc with the syntax highlighting!)
+% \begin{macrocode}
+\edef\texosquerydollar{\expandafter\@texosquery@gobble\string\$}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextdollar}
+%\changes{1.2}{2017-03-23}{new}
+%Textual dollar.
+% \begin{macrocode}
+\def\texosquerytextdollar{\$}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypercent}
+%\changes{1.2}{2017-03-23}{new}
+%Literal percent.
+% \begin{macrocode}
+\edef\texosquerypercent{\expandafter\@texosquery@gobble\string\%}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextpercent}
+%\changes{1.2}{2017-03-23}{new}
+%Textual percent.
+% \begin{macrocode}
+\def\texosquerytextpercent{\%}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryexclam}
+%\changes{1.2}{2017-03-23}{new}
+%Literal exclamation.
+% \begin{macrocode}
+\edef\texosqueryexclam{\string!}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextexclam}
+%\changes{1.2}{2017-03-23}{new}
+%Textual exclamation.
+% \begin{macrocode}
+\def\texosquerytextexclam{!}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryquestion}
+%\changes{1.2}{2017-03-23}{new}
+%Literal question mark.
+% \begin{macrocode}
+\edef\texosqueryquestion{\string?}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextquestion}
+%\changes{1.2}{2017-03-23}{new}
+%Textual question mark.
+% \begin{macrocode}
+\def\texosquerytextquestion{?}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\begin{macro}{\texosqueryliteralspace}
+%\changes{1.2}{2017-03-23}{new}
+%Literal space.
+% \begin{macrocode}
+\edef\texosqueryliteralspace{\expandafter\string\space}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytextspace}
+%\changes{1.2}{2017-03-23}{new}
+%Textual space. (Don't allow it to expand while it's being fetched
+%from \cs{texosquery} just in case it disappears.)
+% \begin{macrocode}
+\def\texosquerytextspace{\noexpand\space}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@D}
+%\changes{1.2}{2017-03-23}{new}
+%The \verb|D| identifier in PDF date-time formats need to have
+%category code 12. This is only used by methods that return results
+%in the form:
+%
+%\verb|D:|\meta{YYYY}\meta{MM}\marg{DD}\meta{HH}\meta{mm}\meta{ss}\meta{TZh}\verb|'|\meta{TZm}\verb|'|
+% \begin{macrocode}
+\edef\@texosquery@D{\string D}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@enableshortcs}
+%\changes{1.2}{2017-03-23}{new}
+%\hypertarget{enableshortcs}{Enable shortcut commands.}
+% \begin{macrocode}
+\def\@texosquery@enableshortcs{%
+% \end{macrocode}
+%Thesre are for the date-time and numeric patterns.
+% \begin{macrocode}
+ \def\patdtf{\noexpand\texosquerydtf}%
+ \def\patpmnumfmt{\noexpand\texosquerypatplusminus}%
+ \def\patnumfmt{\noexpand\texosquerypatnum}%
+ \def\patsinumfmt{\noexpand\texosquerypatsinum}%
+ \def\patdecfmt{\noexpand\texosquerypatdec}%
+ \def\patpcur{\noexpand\texosquerypatprefixcurrency}%
+ \def\patpicur{\noexpand\texosquerypatprefixicurrency}%
+ \def\patscur{\noexpand\texosquerypatsuffixcurrency}%
+ \def\patsicur{\noexpand\texosquerypatsuffixicurrency}%
+ \def\patstr{\noexpand\texosquerypatstr}%
+ \def\patapo{\noexpand\texosquerypatquote}%
+ \def\patdgt{\noexpand\texosquerypatdigit}%
+ \def\patdgtnz{\noexpand\texosquerypatdigitnozero}%
+ \def\patmsg{\noexpand\texosquerypatminus}%
+ \def\patngp{\noexpand\texosquerypatgroupsep}%
+ \def\patppct{\noexpand\texosquerypatprefixpercent}%
+ \def\patspct{\noexpand\texosquerypatsuffixpercent}%
+ \def\patppml{\noexpand\texosquerypatprefixpermill}%
+ \def\patspml{\noexpand\texosquerypatsuffixpermill}%
+% \end{macrocode}
+%Hook to adjust the processing of non-ASCII characters.
+% \begin{macrocode}
+ \def\twrp{\texosquerynonasciiwrap}%
+ \def\fwrp{\texosquerynonasciidetokwrap}%
+% \end{macrocode}
+%Locally redefine some more commands that may occur in
+%\app{texosquery}'s return value (via the \texttt{escapeSpChars}
+%method in \file{TeXOSQuery.java}). The \texttt{t} prefix indicates
+%textual commands and the \texttt{f} prefix indicates literal
+%characters, for example, in file names.
+% \begin{macrocode}
+ \let\fbks\texosquerybackslash
+ \let\tbks\texosquerytextbackslash
+ \let\flbr\texosqueryleftbrace
+ \let\tlbr\texosquerytextleftbrace
+ \let\frbr\texosqueryrightbrace
+ \let\trbr\texosquerytextrightbrace
+ \let\fhsh\texosqueryhash
+ \let\thsh\texosquerytexthash
+ \let\fusc\texosqueryunderscore
+ \let\tusc\texosquerytextunderscore
+ \let\fgrv\texosquerybacktick
+ \let\tgrv\texosquerytextbacktick
+ \let\fapo\texosqueryclosequote
+ \let\tapo\texosquerytextclosequote
+ \let\fdqt\texosquerydoublequote
+ \let\tdqt\texosquerytextdoublequote
+ \let\fspc\texosqueryliteralspace
+ \let\tspc\texosquerytextspace
+ \let\fcln\texosquerycolon
+ \let\tcln\texosquerytextcolon
+ \let\fscl\texosquerysemicolon
+ \let\tscl\texosquerytextsemicolon
+ \let\feql\texosqueryequals
+ \let\teql\texosquerytextequals
+ \let\fhyn\texosqueryhyphen
+ \let\thyn\texosquerytexthyphen
+ \let\fpls\texosqueryplus
+ \let\tpls\texosquerytextplus
+ \let\ftld\texosquerytilde
+ \let\ttld\texosquerytexttilde
+ \let\fcir\texosquerycircum
+ \let\tcir\texosquerytextcircum
+ \let\famp\texosqueryampersand
+ \let\tamp\texosquerytextampersand
+ \let\fslh\texosqueryslash
+ \let\tslh\texosquerytextslash
+ \let\fpct\texosquerypercent
+ \let\tpct\texosquerytextpercent
+ \let\fexc\texosqueryexclam
+ \let\texc\texosquerytextexclam
+ \let\fque\texosqueryquestion
+ \let\tque\texosquerytextquestion
+ \let\fles\texosquerylessthan
+ \let\tles\texosquerytextlessthan
+ \let\fgre\texosquerygreaterthan
+ \let\tgre\texosquerytextgreaterthan
+ \let\fdol\texosquerydollar
+ \let\tdol\texosquerytextdollar
+ \let\fdot\texosqueryperiod
+ \let\tdot\texosquerytextperiod
+ \let\fcom\texosquerycomma
+ \let\tcom\texosquerytextcomma
+ \let\fopb\texosqueryopenparen
+ \let\topb\texosquerytextopenparen
+ \let\fclb\texosquerycloseparen
+ \let\tclb\texosquerytextcloseparen
+ \let\fosb\texosqueryopensq
+ \let\tosb\texosquerytextopensq
+ \let\fcsb\texosqueryclosesq
+ \let\tcsb\texosquerytextclosesq
+ \let\fast\texosqueryasterisk
+ \let\tast\texosquerytextasterisk
+ \let\fatc\texosqueryatchar
+ \let\tatc\texosquerytextatchar
+ \let\pdfd\@texosquery@D
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuery}
+%Use \app{texosquery} with the option given in the second
+%argument and store the result in control sequence given in the
+%first argument.
+% \begin{macrocode}
+\def\TeXOSQuery#1#2{%
+ \ifTeXOSQueryDryRun
+ \@texosquery@dorestrictedmessage
+ \begingroup
+ \newlinechar=`\^^J
+ \message{^^JTeXOSQuery: \TeXOSInvokerName\space#2^^J}%
+ \endgroup
+ \def#1{}%
+ \else
+ \begingroup
+ \endlinechar=-1\relax
+% \end{macrocode}
+%Locally redefine short commands used by \app{texosquery}
+% \begin{macrocode}
+ \@texosquery@enableshortcs
+% \end{macrocode}
+%Change the category code of some potentially awkward characters.
+%(This should no longer be an issue with the new commands that
+%are now used in the returned text, but \app{texosquery} might be run with the
+%backward compatibility mode on, so this is
+%still needed just in case.)
+% \begin{macrocode}
+ \catcode`\-=12\relax
+ \catcode`\_=12\relax
+ \catcode`\^=12\relax
+ \catcode`\~=12\relax
+ \catcode`\$=12\relax
+ \catcode`\&=12\relax
+ \catcode`\.=12\relax
+ \catcode`\/=12\relax
+ \catcode`\:=12\relax
+ \catcode`\"=12\relax
+ \catcode`\'=12\relax
+ \catcode`\;=12\relax
+ \catcode`\%=12\relax
+ \everyeof{\noexpand}\relax
+ \@texosquery@edef\x{\endgroup\def\noexpand#1{\TeXOSQueryInvoker{#2}}}\x
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFromFile}
+%\changes{1.4}{2017-05-05}{new}
+%This is like \cs{TeXOSQuery} but doesn't use the piped shell
+%escape. Instead, it's for cases where \app{texosquery} is run
+%before \TeX\ and the results have been captured in a file. For
+%example, \verb|texosquery -b > texosqueryresult.tex|
+%The second argument is the file name.
+% \begin{macrocode}
+\def\TeXOSQueryFromFile#1#2{%
+% \end{macrocode}
+%First check that the file exists.
+% \begin{macrocode}
+ \openin0=#2
+ \ifeof0\relax
+ \def#1{}%
+ \else
+ \closein0\relax
+ \begingroup
+ \endlinechar=-1\relax
+ \@texosquery@enableshortcs
+ \catcode`\-=12\relax
+ \catcode`\_=12\relax
+ \catcode`\^=12\relax
+ \catcode`\~=12\relax
+ \catcode`\$=12\relax
+ \catcode`\&=12\relax
+ \catcode`\.=12\relax
+ \catcode`\/=12\relax
+ \catcode`\:=12\relax
+ \catcode`\"=12\relax
+ \catcode`\'=12\relax
+ \catcode`\;=12\relax
+ \catcode`\%=12\relax
+ \everyeof{\noexpand}\relax
+ \@texosquery@edef\x{\endgroup\def\noexpand#1{\texosquery@input #2 }}\x
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\subsubsection{Currency}
+%The \meta{\TeX\ currency} element of \longarg{numeric} and
+%\longarg{locale-data} identifies the currency symbol using
+%\begin{definition}
+%\cs{texosquerycurrency}\marg{label}
+%\end{definition}
+%which simply expands to the appropriate command.
+%\begin{macro}{\texosquerycurrency}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrency#1{%
+ \expandafter\noexpand\csname texosquerycurrency#1\endcsname
+}
+% \end{macrocode}
+%\end{macro}
+%
+%Provide the currency commands that may be returned
+%\app{texosquery} (on expansion of \cs{texosquerycurrency}).
+%Most of these will need redefining as there's no appropriate
+%generic code to use as a default.
+%The \sty{fontawesome} package has the most support for currency
+%symbols, so these are checked first.
+%\begin{macro}{\texosquerycurrencydollar}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faDollar\undefined
+ \def\texosquerycurrencydollar{\$}
+\else
+ \def\texosquerycurrencydollar{\faDollar}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencycent}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\textcent\undefined
+ \def\texosquerycurrencycent{cent}
+\else
+ \def\texosquerycurrencycent{\textcent}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencypound}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faGbp\undefined
+ \ifx\pounds\undefined
+ \def\texosquerycurrencypound{pound}
+ \else
+ \def\texosquerycurrencypound{\pounds}
+ \fi
+\else
+ \def\texosquerycurrencypound{\faGbp}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencysign}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\textcurrency\undefined
+ \def\texosquerycurrencysign{currency-sign}
+\else
+ \def\texosquerycurrencysign{\textcurrency}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyyen}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faYen\undefined
+ \ifx\textyen\undefined
+ \def\texosquerycurrencyyen{yen}
+ \else
+ \def\texosquerycurrencyyen{\textyen}
+ \fi
+\else
+ \def\texosquerycurrencyyen{\faYen}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyecu}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencyecu{ecu}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencycolon}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencycolon{colon}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencycruzeiro}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencycruzeiro{cruzeiro}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyfranc}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencyfranc{franc}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencylira}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\textlira\undefined
+ \def\texosquerycurrencylira{lira}
+\else
+ \def\texosquerycurrencylira{\textlira}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencymill}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencymill{mill}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencynaira}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\textnaira\undefined
+ \def\texosquerycurrencynaira{naira}
+\else
+ \def\texosquerycurrencynaira{\textnaira}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencypeseta}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencypeseta{peseta}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyrupee}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faRupee\undefined
+ \def\texosquerycurrencyrupee{rupee}
+\else
+ \def\texosquerycurrencyrupee{\faRupee}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencywon}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faWon\undefined
+ \ifx\textwon\undefined
+ \def\texosquerycurrencywon{won}
+ \else
+ \def\texosquerycurrencywon{\textwon}
+ \fi
+\else
+ \def\texosquerycurrencywon{\faWon}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencynewsheqel}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faSheqel\undefined
+ \def\texosquerycurrencynewsheqel{newsheqel}
+\else
+ \def\texosquerycurrencynewsheqel{\faSheqel}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencydong}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\textdong\undefined
+ \def\texosquerycurrencydong{dong}
+\else
+ \def\texosquerycurrencydong{\textdong}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyeuro}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faEuro\undefined
+ \ifx\texteuro\undefined
+ \ifx\euro\undefined
+ \def\texosquerycurrencyeuro{euro}
+ \else
+ \def\texosquerycurrencyeuro{\euro}
+ \fi
+ \else
+ \def\texosquerycurrencyeuro{\texteuro}
+ \fi
+\else
+ \def\texosquerycurrencyeuro{\faEuro}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencykip}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencykip{kip}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencytugrik}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencytugrik{tugrik}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencydrachma}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencydrachma{drachma}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencygermanpenny}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencygermanpenny{german-penny}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencypeso}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\textpeso\undefined
+ \def\texosquerycurrencypeso{peso}
+\else
+ \def\texosquerycurrencypeso{\textpeso}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyguarani}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\textguarani\undefined
+ \def\texosquerycurrencyguarani{guarani}
+\else
+ \def\texosquerycurrencyguarani{\textguarani}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyaustral}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencyaustral{austral}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyhryvnia}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencyhryvnia{hryvnia}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencycedi}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\textcolonmonetary\undefined
+ \def\texosquerycurrencycedi{cedi}
+\else
+ \def\texosquerycurrencycedi{\textcolonmonetary}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencylivretournois}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencylivretournois{livre-tournois}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyspesmilo}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencyspesmilo{spesmilo}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencytenge}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencytenge{tenge}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyrupee}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencyrupee{rupee}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyturkishlira}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faTurkishLira\undefined
+ \def\texosquerycurrencyturkishlira{turkish-lira}
+\else
+ \def\texosquerycurrencyturkishlira{\faTurkishLira}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencynordicmark}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencynordicmark{nordic-mark}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencymanat}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerycurrencymanat{manat}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerycurrencyruble}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\ifx\faRuble\undefined
+ \def\texosquerycurrencyruble{ruble}
+\else
+ \def\texosquerycurrencyruble{\faRuble}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\subsubsection{Shortcut Commands}
+%Now for some convenient shortcuts so the user doesn't have to
+%remember the command line options. \cs{string} is used in
+%case the hyphen character has been made active.
+%\begin{macro}{\TeXOSQueryLocale}
+%Query the locale and store the result in the control sequence
+%provided in the argument.
+% \begin{macrocode}
+\def\TeXOSQueryLocale#1{\TeXOSQuery{#1}{\string-l}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryLangTag}
+%Query the language tag and store the result in the control sequence
+%provided in the argument.
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\TeXOSQueryLangTag#1{\TeXOSQuery{#1}{\string-b}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryNumeric}
+%Query the numeric settings for the locale given in the second
+%argument and store the result in the control sequence
+%provided in the argument. Leave the second argument empty for the
+%default locale.
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\TeXOSQueryNumeric#1#2{\TeXOSQuery{#1}{\string-N #2}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryLocaleData}
+%Query the data for the locale given in the second
+%argument and store the result in the control sequence
+%provided in the argument. Leave the second argument empty for the
+%default locale.
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\TeXOSQueryLocaleData#1#2{\TeXOSQuery{#1}{\string-D #2}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryCwd}
+%Query the current working directory.
+% \begin{macrocode}
+\def\TeXOSQueryCwd#1{\TeXOSQuery{#1}{\string-c}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryHome}
+%Query the user's home directory.
+% \begin{macrocode}
+\def\TeXOSQueryHome#1{\TeXOSQuery{#1}{\string-m}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryTmpDir}
+%Query the temporary directory.
+% \begin{macrocode}
+\def\TeXOSQueryTmpDir#1{\TeXOSQuery{#1}{\string-t}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryVersion}
+%Query the operating system version.
+% \begin{macrocode}
+\def\TeXOSQueryVersion#1{\TeXOSQuery{#1}{\string-r}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryArch}
+%Query the operating system architecture.
+% \begin{macrocode}
+\def\TeXOSQueryArch#1{\TeXOSQuery{#1}{\string-a}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryName}
+%Query the operating system name.
+% \begin{macrocode}
+\def\TeXOSQueryName#1{\TeXOSQuery{#1}{\string-o}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryDateTime}
+%\changes{1.2}{2017-03-23}{new}
+%Query the current date and time.
+% \begin{macrocode}
+\def\TeXOSQueryDateTime#1{%
+ \TeXOSQuery{#1}{\string-M}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryTimeZones}
+%\changes{1.2}{2017-03-23}{new}
+%Query the current time zone mappings. Leave the second argument
+%empty for the default locale.
+% \begin{macrocode}
+\def\TeXOSQueryTimeZones#1#2{%
+ \TeXOSQuery{#1}{\string-Z #2}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryNow}
+%\changes{1.1}{2016-07-14}{changed catcode of D to 12}
+%Query the current time stamp.
+% \begin{macrocode}
+\def\TeXOSQueryNow#1{%
+% \end{macrocode}
+% The \texttt{D} needs category code 12 just in case
+% \app{texosquery} is running in a backward compatibility mode that
+% doesn't use \cs{pdfd}.
+% \begin{macrocode}
+ \edef\@texosquery@restore@D{%
+ \noexpand\catcode`\noexpand\D=\the\catcode`\D\relax}%
+ \catcode`\D=12\relax
+ \TeXOSQuery{#1}{\string-n}%
+ \@texosquery@restore@D
+}
+% \end{macrocode}
+%\end{macro}
+%
+%If the file name is supplied using \cs{jobname} it may have
+%double-quotes which will interfere with things.
+%\begin{macro}{\texosquerystripquotes}
+% \begin{macrocode}
+\def\texosquerystripquotes#1{%
+ \@texosquery@stripquotes#1\@mid@texosquery@stripquotes
+ "\relax"\relax\@end@texosquery@stripquotes
+}
+\def\@texosquery@stripquotes#1"#2"{%
+ \@@texosquery@stripquotes#1#2%
+}
+\def\@@texosquery@stripquotes#1\@mid@texosquery@stripquotes#2\@end@texosquery@stripquotes{%
+ #1%
+}
+% \end{macrocode}
+%\end{macro}
+%The restricted mode doesn't permit quotes in the shell escape, so
+%arguments are only quoted in unrestricted mode.
+%\begin{macro}{\@texosquery@argquote}
+%\changes{1.4}{2017-05-05}{new}
+% \begin{macrocode}
+\ifnum\texosquery@shellescape=2\relax
+ \def\@texosquery@argquote#1{#1}
+\else
+ \def\@texosquery@argquote#1{\string'#1\string'}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileDate}
+%\changes{1.1}{2016-07-14}{changed catcode of D to 12}
+%Query the time stamp of the file given in the second argument.
+% \begin{macrocode}
+\def\TeXOSQueryFileDate#1#2{%
+% \end{macrocode}
+% The \texttt{D} needs category code 12 just in case
+% \app{texosquery} is running in a backward compatibility mode that
+% doesn't use \cs{pdfd}.
+% \begin{macrocode}
+ \edef\@texosquery@restore@D{%
+ \noexpand\catcode`\noexpand\D=\the\catcode`\D\relax}%
+ \catcode`\D=12\relax
+ \TeXOSQuery{#1}{\string-d
+ \@texosquery@argquote{\texosquerystripquotes{#2}}}%
+ \@texosquery@restore@D
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileSize}
+%Query the size of the file given in the second argument.
+% \begin{macrocode}
+\def\TeXOSQueryFileSize#1#2{\TeXOSQuery{#1}{\string-s
+ \@texosquery@argquote{\texosquerystripquotes{#2}}}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@filelist}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@filelist#1#2#3#4#5{\TeXOSQuery{#1}{%
+ \string#2 \@texosquery@argquote{#3}
+ \@texosquery@argquote{\texosquerystripquotes{#4}} #5}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileList}
+%List all files in the directory given in the third argument,
+%separated by the second argument.
+% \begin{macrocode}
+\def\TeXOSQueryFileList#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListDateAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by date.
+% \begin{macrocode}
+\def\TeXOSQueryFileListDateAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{date}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListDateDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by date in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFileListDateDes#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{date\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListSizeAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by size.
+% \begin{macrocode}
+\def\TeXOSQueryFileListSizeAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{size}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListSizeDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by size in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFileListSizeDes#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{size\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListNameAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by name.
+% \begin{macrocode}
+\def\TeXOSQueryFileListNameAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{name}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListNameDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFileListNameDes#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{name\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListNameIgnoreCaseAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by case-insensitive name.
+% \begin{macrocode}
+\def\TeXOSQueryFileListNameIgnoreCaseAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{iname}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListNameIgnoreCaseDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by case-insensitive name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFileListNameIgnoreCaseDes#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{iname\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListExtAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by extension.
+% \begin{macrocode}
+\def\TeXOSQueryFileListExtAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{ext}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileListExtDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by extension in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFileListExtDes#1#2#3{%
+ \@texosquery@filelist{#1}{-i}{#2}{#3}{ext\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileList}
+%List all regular files.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileList#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirList}
+%List all sub-directories.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirList#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListDateAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by date.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListDateAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{date}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListDateAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by date.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListDateAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{date}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListDateDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by date in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListDateDes#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{date\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListDateDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by date in descending order.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListDateDes#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{date\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListSizeAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by size.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListSizeAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{size}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListSizeAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by size.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListSizeAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{size}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListSizeDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by size in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListSizeDes#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{size\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListSizeDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by size in descending order.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListSizeDes#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{size\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListNameAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by file name.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListNameAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{name}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListNameAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by file name.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListNameAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{name}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListNameDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by file name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListNameDes#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{name\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListNameDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by name in descending order.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListNameDes#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{name\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListNameIgnoreCaseAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by file case-insensitive name.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListNameIgnoreCaseAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{iname}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListNameIgnoreCaseAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by file case-insensitive name.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListNameIgnoreCaseAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{iname}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListNameIgnoreCaseDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by file case-insensitive name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListNameIgnoreCaseDes#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{iname\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListNameIgnoreCaseDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by case-insensitive name in descending order.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListNameIgnoreCaseDes#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{iname\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListExtAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by file extension.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListExtAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{ext}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListExtAsc}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by file extension.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListExtAsc#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{ext}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryRegularFileListExtDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all regular files sorted by file extension in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryRegularFileListExtDes#1#2#3{%
+ \@texosquery@filelist{#1}{-ir}{#2}{#3}{ext\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQuerySubDirListExtDes}
+%\changes{1.2}{2017-03-23}{new}
+%List all sub-directories sorted by extension in descending order.
+% \begin{macrocode}
+\def\TeXOSQuerySubDirListExtDes#1#2#3{%
+ \@texosquery@filelist{#1}{-id}{#2}{#3}{ext\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\begin{macro}{\@texosquery@filterfilelist}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@filterfilelist#1#2#3#4#5#6{%
+ \TeXOSQuery{#1}%
+ {%
+ \string#2
+ \@texosquery@argquote{#3}
+ \@texosquery@argquote{#4}
+ \@texosquery@argquote{\texosquerystripquotes{#5}} #6%
+ }%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileList}
+%Filtered list files in the directory given in the fourth argument,
+%separated by the second argument. The third argument is the regular
+%expression used to filter the list. \emph{Take care of backslashes
+%in the regular expression!}
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileList#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListDateAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by date.
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListDateAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{date}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListDateDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by date in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListDateDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{date\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListSizeAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by size.
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListSizeAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{size}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListSizeDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by size in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListSizeDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{size\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListNameAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file name.
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListNameAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{name}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListNameDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListNameDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{name\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListNameIgnoreCaseAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file name (case-insensitive).
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListNameIgnoreCaseAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{iname}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListNameIgnoreCaseDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by name in descending order (case-insensitive).
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListNameIgnoreCaseDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{iname\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListExtAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file extension.
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListExtAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{ext}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterFileListExtDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by extension in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterFileListExtDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-f}{#2}{#3}{#4}{ext\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileList}
+%Filtered list or regular files.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileList#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirList}
+%Filtered list of sub-directories.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirList#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListDateAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by file date.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListDateAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{date}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListDateAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by file date.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListDateAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{date}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListDateDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by file date in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListDateDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{date\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListDateDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by file date in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListDateDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{date\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListSizeAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by file size.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListSizeAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{size}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListSizeAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by file size.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListSizeAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{size}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListSizeDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by file size in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListSizeDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{size\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListSizeDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by file size in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListSizeDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{size\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListNameAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by file name.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListNameAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{name}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListNameAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by file name.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListNameAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{name}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListNameDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by file name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListNameDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{name\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListNameDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by file name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListNameDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{name\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListNameIgnoreCaseAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by case-insensitive file name.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListNameIgnoreCaseAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{iname}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListNameIgnoreCaseAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by case-insensitive file name.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListNameIgnoreCaseAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{iname}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListNameIgnoreCaseDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by case-insensitive file name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListNameIgnoreCaseDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{iname\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListNameIgnoreCaseDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by case-insensitive file name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListNameIgnoreCaseDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{iname\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListExtAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by file extension. (Added for
+%completeness as directories don't tend to have extensions.)
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListExtAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{ext}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListExtAsc}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by file extension.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListExtAsc#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{ext}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterSubDirListExtDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of sub-directories by file extension in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterSubDirListExtDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fd}{#2}{#3}{#4}{ext\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilterRegularFileListExtDes}
+%\changes{1.2}{2017-03-23}{new}
+%Filtered sort of regular files by file extension in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryFilterRegularFileListExtDes#1#2#3#4{%
+ \@texosquery@filterfilelist{#1}{-fr}{#2}{#3}{#4}{ext\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@walk}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@walk#1#2#3#4#5{%
+ \TeXOSQuery{#1}%
+ {%
+ \string-w
+ \@texosquery@argquote{#2}
+ \@texosquery@argquote{#3}
+ \@texosquery@argquote{\texosquerystripquotes{#4}} #5%
+ }%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalk}
+%\changes{1.2}{2017-03-23}{new}
+%Recursive filtered listing of regular files.
+% \begin{macrocode}
+\def\TeXOSQueryWalk#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkDateAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by date.
+% \begin{macrocode}
+\def\TeXOSQueryWalkDateAsc#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{date}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkDateDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by date in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryWalkDateDes#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{date\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkSizeAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file size.
+% \begin{macrocode}
+\def\TeXOSQueryWalkSizeAsc#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{size}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkSizeDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file size in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryWalkSizeDes#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{size\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkNameAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file name.
+% \begin{macrocode}
+\def\TeXOSQueryWalkNameAsc#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{name}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkNameDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file name in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryWalkNameDes#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{name\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkNameIgnoreCaseAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file name (case-insensitive).
+% \begin{macrocode}
+\def\TeXOSQueryWalkNameIgnoreCaseAsc#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{iname}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkNameIgnoreCaseDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file name (case-insensitive) in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryWalkNameIgnoreCaseDes#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{iname\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkExtAsc}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file extension.
+% \begin{macrocode}
+\def\TeXOSQueryWalkExtAsc#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{ext}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryWalkExtDes}
+%\changes{1.2}{2017-03-23}{new}
+%As above, but sort by file extension in descending order.
+% \begin{macrocode}
+\def\TeXOSQueryWalkExtDes#1#2#3#4{%
+ \@texosquery@walk{#1}{#2}{#3}{#4}{ext\string-des}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFileURI}
+%Get the URI of the file given in the second argument.
+% \begin{macrocode}
+\def\TeXOSQueryFileURI#1#2{\TeXOSQuery{#1}{\string-u
+ \@texosquery@argquote{\texosquerystripquotes{#2}}}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryFilePath}
+%Get the canonical path of the file given in the second argument.
+% \begin{macrocode}
+\def\TeXOSQueryFilePath#1#2{\TeXOSQuery{#1}{\string-p
+ \@texosquery@argquote{\texosquerystripquotes{#2}}}}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\TeXOSQueryDirName}
+%\changes{1.1}{2016-07-14}{new}
+%Get the canonical path of the directory containing the file given
+%in the second argument.
+% \begin{macrocode}
+\def\TeXOSQueryDirName#1#2{\TeXOSQuery{#1}{\string-e
+ \@texosquery@argquote{\texosquerystripquotes{#2}}}}
+% \end{macrocode}
+%\end{macro}
+%
+%\subsubsection{Pattern Formats}
+%\label{sec:patternformats}
+%There are two basic types of patterns: date/time or numeric.
+%A pattern is stored in a control sequence using custom markup
+%that's easier for \TeX\ to parse than it would be to parse
+%strings in the form \verb|YYYY-MM| or \verb|#,##0|. This internal
+%pattern format can be obtained through capturing the output
+%of \app{texosquery}'s \shortarg{D} action, but patterns can also be
+%constructed using
+%\begin{definition}
+%\cs{texosquerydefpattern}\marg{cs}\marg{pattern specs}
+%\end{definition}
+%The pattern is stored in \meta{cs}. The \meta{pattern specs} depend
+%on whether a date-time or numeric pattern is required.
+%For a date-time pattern, each date/time element is identified
+%using
+%\begin{definition}
+%\cs{texosquerydtf}\marg{n}\marg{identifier}
+%\end{definition}
+%where \meta{identifier} identifies the element type (such as
+%\verb|M| for month or \verb|s| for seconds) and \meta{n}
+%indicates how the element should be formatted, where \meta{n}
+%is an integer from 1 to 4. For example if \meta{n} is 2
+%and \meta{identifier} is \verb|M|, then this indicates the
+%\verb|MM| format, which produces a two-digit number.
+%
+%Since it's rather cumbersome to keep typing \cs{texosquerydtf}
+%and it can make for rather hard to read code,
+%\cs{texosquerydefpattern} locally redefines \verb|\%| to expand to
+%\cs{texosquerydtf}. This means that if you do, for example:
+%\begin{verbatim}
+%\texosquerydefpattern{\pattern}{\%2d/\%2M/\%4y}
+%\end{verbatim}
+%then \cs{pattern} is defined to
+%\begin{verbatim}
+%\texosquerydtf 2d/\texosquerydtf 2M/\texosquerydtf 4y
+%\end{verbatim}
+%When simply used within the document, this just expands to the
+%pattern format. For example:
+%\begin{verbatim}
+%Pattern: \pattern.
+%\end{verbatim}
+%will display \qt{Pattern: dd/MM/yyyy} in the PDF. However,
+%when used with \cs{texosqueryfmtdatetime}, the definition
+%of \cs{texosquerydtf} changes to reproduce the required
+%date/time element.
+%
+%For example:
+%\begin{verbatim}
+%\texosquerydefpattern{\pattern}{\%2d/\%2M/\%4y \%2H:\%2m:\%2s \%2Z}
+%
+%Pattern: \pattern.
+%
+%\TeXOSQueryDateTime{\datetimedata}
+%
+%\ifx\datetimedata\empty
+% Query Failed!
+%\else
+% \expandafter\texosqueryfmtdatetime\expandafter\pattern\datetimedata
+%\fi
+%\end{verbatim}
+%
+%The numeric patterns are rather more complicated. The
+%\meta{pattern specs} now needs to use the following formats:
+%
+%\begin{definition}
+%\cs{texosquerypatnum}\marg{+ve/-ve numeric pattern}
+%\end{definition}
+%This is a numeric pattern applied to a number regardless of
+%whether the number is positive or negative. (If negative, the
+%minus sign is automatically inserted.) This is rather a long
+%and cumbersome command to type, so \cs{texosquerydefpattern}
+%locally defines \cs{numfmt} to expand to it.
+%
+%\begin{definition}
+%\cs{texosquerypatplusminus}\marg{+ve numeric pattern}\marg{-ve numeric
+%pattern}
+%\end{definition}
+%This provides a pattern \meta{+ve numeric pattern} to use if the
+%number is positive and a patter \meta{-ve numeric pattern} to use
+%if the number is negative. Again \cs{texosquerydefpattern}
+%locally defines a shortcut, \cs{pmnumfmt}, to expand to this
+%command.
+%
+%\begin{definition}
+%\cs{texosquerypatsinum}\marg{decimal pattern}\marg{mantissa
+%pattern}
+%\end{definition}
+%This provides a pattern to use for SI numbers where \meta{decimal
+%pattern} is a pattern for the decimal number part (before the
+%exponent symbol) and \meta{mantissa} is the pattern for the integer
+%part in the mantissa (after the exponent symbol).
+%The locally defined shortcut is \cs{sinumfmt}. The \meta{decimal
+%pattern} will typically be in the form:
+%\begin{definition}
+%\cs{texosquerypatdec}\marg{integer pattern}\marg{fraction pattern}
+%\end{definition}
+%This indicates a decimal pattern where the \meta{integer pattern}
+%is applied to the part before the decimal separator and
+%\meta{fraction pattern} is applied to the part after the separator. The shortcut is \cs{decfmt}.
+%
+%\begin{definition}
+%\cs{texosquerypatprefixcurrency}\marg{decimal pattern}\marg{text}
+%\end{definition}
+%This indicates a currency pattern with a prefixed currency symbol where \meta{text} is inserted
+%before the currency symbol. The shortcut is \cs{pcur}.
+%The \meta{decimal pattern} will typically use
+%\cs{texosquerypatdec}\marg{int}\meta{frac}. (Similarly for the
+%following.)
+%
+%\begin{definition}
+%\cs{texosquerypatprefixicurrency}\marg{decimal pattern}\marg{text}
+%\end{definition}
+%This indicates an international currency pattern with a prefixed
+%international currency symbol where \meta{text} is inserted
+%before the symbol. The shortcut is \cs{picur}.
+%
+%\begin{definition}
+%\cs{texosquerypatsuffixcurrency}\marg{decimal pattern}\marg{text}
+%\end{definition}
+%This indicates a currency pattern with a suffixed international
+%currency symbol where \meta{text} is inserted
+%after the currency symbol. The shortcut is \cs{scur}.
+%
+%\begin{definition}
+%\cs{texosquerypatsuffixicurrency}\marg{decimal pattern}\marg{text}
+%\end{definition}
+%This indicates an international currency pattern with a suffixed
+%international currency symbol where \meta{text} is inserted
+%after the symbol. The shortcut is \cs{sicur}.
+%
+%\begin{definition}
+%\cs{texosquerypatprefixpercent}\marg{decimal pattern}\marg{text}
+%\end{definition}
+%This indicates a percentage pattern with a prefixed percent symbol where \meta{text} is inserted
+%before the symbol. The shortcut is \cs{ppct}.
+%
+%\begin{definition}
+%\cs{texosquerypatsuffixpercent}\marg{decimal pattern}\marg{text}
+%\end{definition}
+%This indicates a percentage pattern with a suffixed percent symbol where \meta{text} is inserted
+%after the symbol. The shortcut is \cs{spct}.
+%
+%\begin{definition}
+%\cs{texosquerypatprefixpermill}\marg{decimal pattern}\marg{text}
+%\end{definition}
+%This indicates a per-mill pattern with a prefixed per-mill symbol where \meta{text} is inserted
+%before the symbol. The shortcut is \cs{ppml}.
+%
+%\begin{definition}
+%\cs{texosquerypatsuffixpermill}\marg{decimal pattern}\marg{text}
+%\end{definition}
+%This indicates a per-mill pattern with a suffixed per-mill symbol where \meta{text} is inserted
+%after the symbol. The shortcut is \cs{spml}.
+%
+%\begin{important}
+%The integer parts \meta{integer part}, \meta{fraction part}
+%and \meta{mantissa} must have \emph{exactly} ten digit identifiers.
+%(\TeX\ can't reach 11 digit numbers.)
+%\end{important}
+%
+%There are two types of digit identifiers:
+%\begin{definition}
+%\cs{texosquerypatdigit}
+%\end{definition}
+%This indicates a digit that must be displayed, even if it's not
+%significant (for example a leading zero). The shortcut command is \verb|\0| (backslash zero).
+%
+%\begin{definition}
+%\cs{texosquerypatdigitnozero}
+%\end{definition}
+%This indicates a digit that should only be displayed if it's
+%significant. (For example, if it's a leading zero, it's not shown.)
+%The shortcut command is \verb|\#| (backslash hash).
+%
+%The number group separator can be inserted using
+%\begin{definition}
+%\cs{texosquerypatgroupsep}
+%\end{definition}
+%The shortcut command is \verb|\,| (backslash comma).
+%
+%The sign can be inserted using
+%\begin{definition}
+%\cs{texosquerypatminus}
+%\end{definition}
+%This ensures the sign is displayed even if the number is positive.
+%The shortcut command is \verb|\-| (backslash hyphen).
+%
+%Here's an example of a decimal pattern:
+%\begin{verbatim}
+%\texosquerydefpattern{\numpattern}{%
+% \numfmt{\decfmt{\#\,\#\#\#\,\#\#\#\,\#\#\0}{\0\#\#\#\#\#\#\#\#\#}}}
+%\end{verbatim}
+%The pattern can be applied to a number using \cs{texosqueryfmtnumber}:
+%\begin{verbatim}
+%\texosqueryfmtnumber{\numpattern}{123}{4567}{2}
+%\end{verbatim}
+%which produces: 12,345.67 (the group and decimal separators can be
+%redefined as appropriate).
+%
+%Here's an example of a scientific number:
+%\begin{verbatim}
+%\texosquerydefpattern{\sinumpattern}{%
+% \sinumfmt
+% {\decfmt{\#\,\#\#\#\,\#\#\#\,\#\#\0}{\0\#\#\#\#\#\#\#\#\#}}%
+% {\-\#\#\#\#\#\#\#\#\0\0}%
+%}
+%\end{verbatim}
+%The pattern can be applied to a number:
+%\begin{verbatim}
+%\texosqueryfmtnumber{\sinumpattern}{1}{234567}{3}
+%\end{verbatim}
+%which produces: 1.234567E+03
+%
+%Here's an integer pattern:
+%\begin{verbatim}
+%\texosquerydefpattern{\intpattern}{%
+% \patnumfmt{\#\,\#\#\#\,\#\#\#\,\#\#\0}}
+%\end{verbatim}
+%The pattern applied to a number:
+%\begin{verbatim}
+%\texosqueryfmtnumber{\intpattern}{123}{4567}{2}
+%\end{verbatim}
+%which produces: 12,345 (the fractional part has been omitted).
+%
+%Here's a currency pattern that applies a different format for
+%positive and negative numbers:
+%\begin{verbatim}
+%\texosquerydefpattern{\curpattern}{%
+% \pmnumfmt
+% {\pcur{\decfmt{\#\,\#\#\#\,\#\#\#\,\#\#\0}{\0\0\#\#\#\#\#\#\#\#}}{}}%
+% {\pcur{\decfmt{\#\,\#\#\#\,\#\#\#\,\#\#\0}{\0\0\#\#\#\#\#\#\#\#}}{\-}}}
+%\end{verbatim}
+%This uses the \meta{text} part of \cs{pcur} to insert the sign
+%before the currency symbol (but only for negative values).
+%\begin{verbatim}
+%\texosqueryfmtnumber{\curpattern}{-1234567}{0}{0}
+%\end{verbatim}
+%This produces: $-$\$12,345,678.00 (again the symbol and separators
+%can be redefined as appropriate).
+%
+%Here's an example of a percentage pattern:
+%\begin{verbatim}
+%\texosquerydefpattern{\pcpattern}{%
+% \numfmt{\spct{\#\,\#\#\#\,\#\#\#\,\#\#\0}{}}}
+%\end{verbatim}
+%The pattern can similarly be applied to a number using
+%\cs{texosqueryfmtnumber}.
+%
+%\begin{macro}{\texosquerydtf}
+%\changes{1.2}{2017-03-23}{new}
+%Date/time format placeholder. The second argument is the
+%placeholder character and the first argument is the number of
+%occurrences of that character in the placeholder. The default
+%definition just converts it back to pattern format used by
+%Java's
+%\href{http://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html}{SimpleDateFormat
+%class}. The pattern interprets $\ge4$ as a single case, so this
+%will only produce a maximum of four characters.
+% \begin{macrocode}
+\def\texosquerydtf#1#2{%
+ \ifcase#1
+ \or
+ #2%
+ \or
+ #2#2%
+ \or
+ #2#2#2%
+ \else
+ #2#2#2#2%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%The following commands are used to display the pattern in the
+%document text to reproduce the pattern string recognised by Java.
+%This is provided for debugging to check the pattern. In most cases
+%the pattern will be applied to a number rather than simply
+%displayed.
+%
+%\begin{macro}{\texosquerypatstr}
+%\changes{1.2}{2017-03-23}{new}
+%Quoted string contained in number format.
+% \begin{macrocode}
+\def\texosquerypatstr#1{'#1'}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatquote}
+%\changes{1.2}{2017-03-23}{new}
+%Literal quote contained in number format.
+% \begin{macrocode}
+\def\texosquerypatquote{''}
+% \end{macrocode}
+%\end{macro}
+%Number format place holders.
+%\begin{macro}{\texosquerypatplusminus}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatplusminus#1#2{#1;#2}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatnum}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatnum#1{#1}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatsinum}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatsinum#1#2{#1E#2}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatdec}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatdec#1#2{#1.#2}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatprefixcurrency}
+%\changes{1.2}{2017-03-23}{new}
+%First argument is a number, the second is optional text before the
+%currency symbol. This will require UTF-8 support otherwise it will
+%need redefining as appropriate. (Similarly for the other currency
+%commands and for the per-mill commands.)
+% \begin{macrocode}
+\def\texosquerypatprefixcurrency#1#2{#2¤#1}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatprefixicurrency}
+%\changes{1.2}{2017-03-23}{new}
+%As above but use international currency symbol.
+% \begin{macrocode}
+\def\texosquerypatprefixicurrency#1#2{#2¤¤#1}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatsuffixcurrency}
+%\changes{1.2}{2017-03-23}{new}
+%First argument is a number, the second is optional text after the
+%currency symbol.
+% \begin{macrocode}
+\def\texosquerypatsuffixcurrency#1#2{#1¤#2}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatsuffixicurrency}
+%\changes{1.2}{2017-03-23}{new}
+%As above but use international currency symbol.
+% \begin{macrocode}
+\def\texosquerypatsuffixicurrency#1#2{#1¤¤#2}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatdigit}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatdigit{0}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatdigitnozero}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatdigitnozero{\#}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatminus}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatminus{-}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatgroupsep}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatgroupsep{,}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatprefixpercent}
+%\changes{1.2}{2017-03-23}{new}
+%The first argument is the value, the second argument is optional
+%text before the percent symbol.
+% \begin{macrocode}
+\def\texosquerypatprefixpercent#1#2{#2\%#1}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatsuffixpercent}
+%\changes{1.2}{2017-03-23}{new}
+%The first argument is the value, the second argument is optional
+%text after the percent symbol.
+% \begin{macrocode}
+\def\texosquerypatsuffixpercent#1#2{#1\%#2}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatprefixpermill}
+%\changes{1.2}{2017-03-23}{new}
+%The first argument is the value, the second argument is optional
+%text before the per-mill symbol.
+% \begin{macrocode}
+\def\texosquerypatprefixpermill#1#2{#2‰#1}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatsuffixpermill}
+%\changes{1.2}{2017-03-23}{new}
+%The first argument is the value, the second argument is optional
+%text after the per-mill symbol.
+% \begin{macrocode}
+\def\texosquerypatsuffixpermill#1#2{#1‰#2}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@pattern@shortcuts}
+%Provide much shorter cuts for the convenience of directly defining
+%patterns with \cs{texosquerydefpattern}.
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@pattern@shortcuts{%
+ \def\%{\noexpand\texosquerydtf}%
+ \def\0{\noexpand\texosquerypatdigit}%
+ \def\#{\noexpand\texosquerypatdigitnozero}%
+ \def\-{\noexpand\texosquerypatminus}%
+ \def\,{\noexpand\texosquerypatgroupsep}%
+ \def\numfmt{\noexpand\texosquerypatnum}%
+ \def\pmnumfmt{\noexpand\texosquerypatplusminus}%
+ \def\sinumfmt{\noexpand\texosquerypatsinum}%
+ \def\decfmt{\noexpand\texosquerypatdec}%
+ \def\pcur{\noexpand\texosquerypatprefixcurrency}%
+ \def\picur{\noexpand\texosquerypatprefixicurrency}%
+ \def\scur{\noexpand\texosquerypatsuffixcurrency}%
+ \def\sicur{\noexpand\texosquerypatsuffixicurrency}%
+ \def\ppct{\noexpand\texosquerypatprefixpercent}%
+ \def\spct{\noexpand\texosquerypatsuffixpercent}%
+ \def\ppml{\noexpand\texosquerypatprefixpermill}%
+ \def\spml{\noexpand\texosquerypatsuffixpermill}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerydefpattern}
+%Define a new pattern using the shortcut markup. The first argument
+%is the name of the control sequence in which to store the pattern
+%provided in the second argument. Be careful of any fragile commands
+%within the second argument. They will need protecting!
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerydefpattern#1#2{%
+ \begingroup
+ \@texosquery@pattern@shortcuts
+ \@texosquery@edef\x{\endgroup\def\noexpand#1{#2}}\x
+}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\subsubsection{Applying Date-Time Patterns}
+%\label{sec:applydatetimepatterns}
+%In order to apply date-time patterns, we need all the information about the
+%date or time we're trying to format.
+%\begin{enumerate}
+%\item Era needed by the \texttt{G} designator. Java
+%identifies the era by an integer (0 = BC and 1 = AD).
+%\item Era text (e.g. AD) can be supplied by a macro.
+%\item Year needed by the \texttt{y} designator.
+%\item Week year needed by the \texttt{Y} designator.
+%\item Month in year needed by the \texttt{M} or \texttt{L}
+%designators.
+%\item Month name needed by the \texttt{M} or \texttt{L}
+%designators. This can be provided as macros that convert the month
+%number to the name. Four macros are needed: short, full, standalone
+%short and standalone full.
+%\item Week in year needed by the \texttt{w} designator.
+%\item Week in month needed by the \texttt{W} designator.
+%\item Day in year needed by the \texttt{D} designator.
+%\item Day in month needed by the \texttt{d} designator.
+%\item Day of week in month needed by the \texttt{F} designator.
+%\item Day name in week needed by the \texttt{E} designator.
+%This can be provided as a macros that accepts the day of week
+%number. The full form is needed for 4 letter patterns otherwise a
+%short form.
+%\item Day number of week (1 = Monday, 7 = Sunday) needed by the
+%\texttt{u} designator. This means that
+%the above day of week name macros needs to use Monday=1 base indexing.
+%This means that \cs{pgfcalendarweekdayname} can't be used directly.
+%\item AM/PM identifier needed by the \texttt{a}
+%designator. Assume 0 = AM and 1 = PM to match Java.
+%\item AM/PM text can be provided by a macro.
+%\item Hour of the day (0-23) needed by the \texttt{H}
+%designator.
+%\item Hour in day (1-24) needed by the \texttt{k}
+%designator.
+%\item Hour in am/pm (0-11) needed by the \texttt{K} designator.
+%\item Hour in am/pm (1-12) needed by the \texttt{h} designator.
+%\item Minute in hour needed by the \texttt{m} designator.
+%\item Second in minute needed by the \texttt{s} designator.
+%\item Millisecond needed by the \texttt{S} designator.
+%\item Time zone needed by the \texttt{z}, \texttt{Z} and \texttt{X}
+%designators. This will require macros for converting the time zone
+%to each of those formats.
+%\end{enumerate}
+%Supply a general utility command that has enough arguments to pass
+%all the above information. A higher level user command can then be
+%provided that determines all the arguments to provide an easier
+%interface.
+%
+%The arguments need to be the pattern followed by
+%\marg{era id}\marg{year}\marg{week year}\marg{month}\marg{week in
+%year}\marg{week in month}\marg{day in year}\marg{day in month}\marg{day of
+%week in month}\marg{day number of week}\marg{am/pm id}\marg{hour of day
+%(H)}\marg{hour in day (k)}\marg{hour in am/pm (K)}\marg{hour in
+%am/pm (h)}\marg{minute in hour}\marg{second in minute}\marg{millisecond}\marg{time zone}
+%The arguments must all be integers except for the time zone which
+%must be in the form
+%\marg{TZh}\marg{TZm}\marg{id}\marg{dst flag}. These are the time zone hour and
+%min offsets, time zone ID and daylight saving flag (1 if daylight
+%saving in effect otherwise 0). These arguments can all be
+%obtained using the \longarg{date-time} / \shortarg{M} action.
+%
+%We'll need some helper macros to get around the nine argument
+%maximum limit.
+%
+%\begin{macro}{\texosqueryfmtdatetime}
+%\changes{1.2}{2017-03-23}{new}
+%The general utility command to format a pattern. The first argument
+%is the pattern. After that are the date-time data arguments.
+% \begin{macrocode}
+\def\texosqueryfmtdatetime#1{%
+ \def\@texosquery@fmt@dt@pattern{#1}%
+ \@texosquery@fmt@getera
+}
+% \end{macrocode}
+%\end{macro}
+%
+%All the remaining arguments except for the time zone must be
+%integers. These are padded using \cs{@texosquery@paddigits}.
+%
+%\begin{macro}{\@texosquery@fmt@getera}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getera#1{%
+ \edef\@texosquery@fmt@G{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getyear
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getyear}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getyear#1{%
+ \edef\@texosquery@fmt@y{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getweekyear
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getweekyear}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getweekyear#1{%
+ \edef\@texosquery@fmt@Y{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getmonth
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getmonth}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getmonth#1{%
+ \edef\@texosquery@fmt@M{\@texosquery@paddigits{#1}}%
+ \let\@texosquery@fmt@L\@texosquery@fmt@M
+ \@texosquery@fmt@getweekinyear
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getweekinyear}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getweekinyear#1{%
+ \edef\@texosquery@fmt@w{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getweekinmonth
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getweekinmonth}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getweekinmonth#1{%
+ \edef\@texosquery@fmt@W{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getdayinyear
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getdayinyear}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getdayinyear#1{%
+ \edef\@texosquery@fmt@D{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getdayinmonth
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getdayinmonth}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getdayinmonth#1{%
+ \edef\@texosquery@fmt@d{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getdayofweekinmonth
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getdayofweekinmonth}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getdayofweekinmonth#1{%
+ \edef\@texosquery@fmt@F{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getdaynumberofweek
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getdaynumberofweek}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getdaynumberofweek#1{%
+ \edef\@texosquery@fmt@u{\@texosquery@paddigits{#1}}%
+ \let\@texosquery@fmt@E\@texosquery@fmt@u
+ \@texosquery@fmt@getampm
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getampm}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getampm#1{%
+ \edef\@texosquery@fmt@a{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@gethourindayH
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@gethourindayH}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@gethourindayH#1{%
+ \edef\@texosquery@fmt@H{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@gethourindayk
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@gethourindayk}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@gethourindayk#1{%
+ \edef\@texosquery@fmt@k{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@gethourinampmK
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@gethourinampmK}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@gethourinampmK#1{%
+ \edef\@texosquery@fmt@K{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@gethourinampmh
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@gethourinampmh}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@gethourinampmh#1{%
+ \edef\@texosquery@fmt@h{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getminute
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getminute}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getminute#1{%
+ \edef\@texosquery@fmt@m{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getsecond
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getsecond}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getsecond#1{%
+ \edef\@texosquery@fmt@s{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@getmillisecond
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@getmillisecond}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@getmillisecond#1{%
+ \edef\@texosquery@fmt@S{\@texosquery@paddigits{#1}}%
+ \@texosquery@fmt@gettimezone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@gettimezone}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@gettimezone#1{%
+ \def\@texosquery@fmt@Z{#1}%
+ \def\@texosquery@fmt@z{#1}%
+ \def\@texosquery@fmt@X{#1}%
+% \end{macrocode}
+%All data now supplied. Temporarily redefine pattern markup and
+%process the pattern.
+% \begin{macrocode}
+ \begingroup
+ \@texosquery@setup@dtpattern
+ \@texosquery@fmt@dt@pattern
+ \endgroup
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@setup@dtpattern}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@setup@dtpattern{%
+ \let\texosquerydtf\@texosquery@fmt@dtf
+ \let\texosquerypatstr\texosquerypatfmtstr
+ \let\texosquerypatquote\texosquerypatfmtquote
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@paddigits@pos}
+%\changes{1.2}{2017-03-23}{new}
+%Pad positive number to 10 digits. \TeX\ can't reach 11 digits, so this is
+%the maximum representation.
+% \begin{macrocode}
+\def\@texosquery@paddigits@pos#1{%
+ \ifnum#1<10
+ 000000000\number#1
+ \else
+ \ifnum#1<100
+ 00000000\number#1
+ \else
+ \ifnum#1<1000
+ 0000000\number#1
+ \else
+ \ifnum#1<10000
+ 000000\number#1
+ \else
+ \ifnum#1<100000
+ 00000\number#1
+ \else
+ \ifnum#1<1000000
+ 0000\number#1
+ \else
+ \ifnum#1<10000000
+ 000\number#1
+ \else
+ \ifnum#1<100000000
+ 00\number#1
+ \else
+ \ifnum#1<1000000000
+ 0\number#1
+ \else
+ \number#1
+ \fi
+ \fi
+ \fi
+ \fi
+ \fi
+ \fi
+ \fi
+ \fi
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@paddigits}
+%\changes{1.2}{2017-03-23}{new}
+%This will expand to 11 characters (sign followed by 10 digits).
+% \begin{macrocode}
+\def\@texosquery@paddigits#1{%
+ \ifnum#1<0
+% \end{macrocode}
+%Move the minus sign outside.
+% \begin{macrocode}
+ -\expandafter\@texosquery@paddigits@pos\expandafter
+ {\@texosquery@gobble#1}%
+ \else
+ +\@texosquery@paddigits@pos{#1}%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@paddigits@trailing}
+%\changes{1.2}{2017-03-23}{new}
+%Pad trailing zeros.
+% \begin{macrocode}
+\def\@texosquery@paddigits@trailing#1{%
+ \expandafter\@texosquery@tenoften@then@gobble
+ #10000000000\@texosquery@end@tenoften
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@tenoften@then@gobble}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@tenoften@then@gobble#1#2#3#4#5#6#7#8#9{%
+ #1#2#3#4#5#6#7#8#9%
+ \@texosquery@lastoften@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@lastoften@gobble}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@lastoften@gobble#1#2\@texosquery@end@tenoften{#1}
+% \end{macrocode}
+%\end{macro}
+%
+%Provide commands to select certain digits. (Sign not included.)
+%
+%\begin{macro}{\@texosquery@firstoften}
+%\changes{1.2}{2017-03-23}{new}
+%First of ten.
+% \begin{macrocode}
+\def\@texosquery@firstoften#1#2#3#4#5#6#7#8#9{%
+ #1%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@secondoften}
+%\changes{1.2}{2017-03-23}{new}
+%Second of ten.
+% \begin{macrocode}
+\def\@texosquery@secondoften#1#2#3#4#5#6#7#8#9{%
+ #2%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@thirdoften}
+%\changes{1.2}{2017-03-23}{new}
+%Third of ten.
+% \begin{macrocode}
+\def\@texosquery@thirdoften#1#2#3#4#5#6#7#8#9{%
+ #3%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fourthoften}
+%\changes{1.2}{2017-03-23}{new}
+%Fourth of ten.
+% \begin{macrocode}
+\def\@texosquery@fourthoften#1#2#3#4#5#6#7#8#9{%
+ #4%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fifthoften}
+%\changes{1.2}{2017-03-23}{new}
+%Fifth of ten.
+% \begin{macrocode}
+\def\@texosquery@fifthoften#1#2#3#4#5#6#7#8#9{%
+ #5%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@sixthoften}
+%\changes{1.2}{2017-03-23}{new}
+%Sixth of ten.
+% \begin{macrocode}
+\def\@texosquery@sixthoften#1#2#3#4#5#6#7#8#9{%
+ #6%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@seventhoften}
+%\changes{1.2}{2017-03-23}{new}
+%Seventh of ten.
+% \begin{macrocode}
+\def\@texosquery@seventhoften#1#2#3#4#5#6#7#8#9{%
+ #7%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@eighthoften}
+%\changes{1.2}{2017-03-23}{new}
+%Eighth of ten.
+% \begin{macrocode}
+\def\@texosquery@eighthoften#1#2#3#4#5#6#7#8#9{%
+ #8%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@ninthoften}
+%\changes{1.2}{2017-03-23}{new}
+%Ninth of ten.
+% \begin{macrocode}
+\def\@texosquery@ninthoften#1#2#3#4#5#6#7#8#9{%
+ #9%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@tenthoften}
+%\changes{1.2}{2017-03-23}{new}
+%Tenth of ten.
+% \begin{macrocode}
+\def\@texosquery@tenthoften#1#2#3#4#5#6#7#8#9{%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%Now macros to select first $n$ of ten.
+%
+%\begin{macro}{@texosquery@firsttwooften}
+%\changes{1.2}{2017-03-23}{new}
+%First two of ten.
+% \begin{macrocode}
+\def\@texosquery@firsttwooften#1#2#3#4#5#6#7#8#9{%
+ #1#2%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@firstthreeoften}
+%\changes{1.2}{2017-03-23}{new}
+%First three of ten.
+% \begin{macrocode}
+\def\@texosquery@firstthreeoften#1#2#3#4#5#6#7#8#9{%
+ #1#2#3%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@firstfouroften}
+%\changes{1.2}{2017-03-23}{new}
+%First four of ten.
+% \begin{macrocode}
+\def\@texosquery@firstfouroften#1#2#3#4#5#6#7#8#9{%
+ #1#2#3#4%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@firstfiveoften}
+%\changes{1.2}{2017-03-23}{new}
+%First five of ten.
+% \begin{macrocode}
+\def\@texosquery@firstfiveoften#1#2#3#4#5#6#7#8#9{%
+ #1#2#3#4#5%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@firstsixoften}
+%\changes{1.2}{2017-03-23}{new}
+%First six of ten.
+% \begin{macrocode}
+\def\@texosquery@firstsixoften#1#2#3#4#5#6#7#8#9{%
+ #1#2#3#4#5#6%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@firstsevenoften}
+%\changes{1.2}{2017-03-23}{new}
+%First seven of ten.
+% \begin{macrocode}
+\def\@texosquery@firstsevenoften#1#2#3#4#5#6#7#8#9{%
+ #1#2#3#4#5#6#7%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@firsteightoften}
+%\changes{1.2}{2017-03-23}{new}
+%First eight of ten.
+% \begin{macrocode}
+\def\@texosquery@firsteightoften#1#2#3#4#5#6#7#8#9{%
+ #1#2#3#4#5#6#7#8%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@firstnineoften}
+%\changes{1.2}{2017-03-23}{new}
+%First nine of ten.
+% \begin{macrocode}
+\def\@texosquery@firstnineoften#1#2#3#4#5#6#7#8#9{%
+ #1#2#3#4#5#6#7#8#9%
+% \end{macrocode}
+%Grab tenth argument and discard.
+% \begin{macrocode}
+ \@texosquery@gobble
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@alltenoften}
+%\changes{1.2}{2017-03-23}{new}
+%All ten.
+% \begin{macrocode}
+\def\@texosquery@alltenoften#1#2#3#4#5#6#7#8#9{%
+ #1#2#3#4#5#6#7#8#9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%Select last $n$ of ten.
+%
+%\begin{macro}{@texosquery@lasttwooften}
+%\changes{1.2}{2017-03-23}{new}
+%Last two of ten.
+% \begin{macrocode}
+\def\@texosquery@lasttwooften#1#2#3#4#5#6#7#8#9{%
+ #9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@lastthreeoften}
+%\changes{1.2}{2017-03-23}{new}
+%Last three of ten.
+% \begin{macrocode}
+\def\@texosquery@lastthreeoften#1#2#3#4#5#6#7#8#9{%
+ #8#9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@lastfouroften}
+%\changes{1.2}{2017-03-23}{new}
+%Last four of ten.
+% \begin{macrocode}
+\def\@texosquery@lastfouroften#1#2#3#4#5#6#7#8#9{%
+ #7#8#9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@lastfiveoften}
+%\changes{1.2}{2017-03-23}{new}
+%Last five of ten.
+% \begin{macrocode}
+\def\@texosquery@lastfiveoften#1#2#3#4#5#6#7#8#9{%
+ #6#7#8#9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@lastsixoften}
+%\changes{1.2}{2017-03-23}{new}
+%Last six of ten.
+% \begin{macrocode}
+\def\@texosquery@lastsixoften#1#2#3#4#5#6#7#8#9{%
+ #5#6#7#8#9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@lastsevenoften}
+%\changes{1.2}{2017-03-23}{new}
+%Last seven of ten.
+% \begin{macrocode}
+\def\@texosquery@lastsevenoften#1#2#3#4#5#6#7#8#9{%
+ #4#5#6#7#8#9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@lasteightoften}
+%\changes{1.2}{2017-03-23}{new}
+%Last eight of ten.
+% \begin{macrocode}
+\def\@texosquery@lasteightoften#1#2#3#4#5#6#7#8#9{%
+ #3#4#5#6#7#8#9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{@texosquery@lastnineoften}
+%\changes{1.2}{2017-03-23}{new}
+%Last nine of ten.
+% \begin{macrocode}
+\def\@texosquery@lastnineoften#1#2#3#4#5#6#7#8#9{%
+ #2#3#4#5#6#7#8#9%
+ \@texosquery@firstofone
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmtminus}
+%\changes{1.2}{2017-03-23}{new}
+%Minus symbol for use in date-time patterns.
+% \begin{macrocode}
+\def\@texosquery@fmtminus{\texosquerypatfmtminus}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmtplus}
+%\changes{1.2}{2017-03-23}{new}
+%Plus symbol for use in date-time patterns. Omit by default.
+% \begin{macrocode}
+\def\@texosquery@fmtplus{}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmtsign}
+%\changes{1.2}{2017-03-23}{new}
+%Plus or minus sign for use in date-time patterns.
+% \begin{macrocode}
+\def\@texosquery@fmtsign#1{%
+ \ifx#1+\@texosquery@fmtplus\else\@texosquery@fmtminus\fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@atleastonedigit}
+%\changes{1.2}{2017-03-23}{new}
+%At least one digit with leading zeros removed.
+% \begin{macrocode}
+\def\@texosquery@atleastonedigit#1{%
+ \ifnum#1<0
+ \@texosquery@fmtminus\number-#1
+ \else
+ \number#1
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@atleastfourdigits}
+%\changes{1.2}{2017-03-23}{new}
+%At least four digits, possible padded with zeros to make up four. The first
+%argument is the sign, then follow the ten digits.
+% \begin{macrocode}
+\def\@texosquery@atleastfourdigits#1{%
+ \@texosquery@at@leastfourdigits#1\@texosquery@end@atleastfourdigits
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@at@leastfourdigits}
+%\changes{1.2}{2017-03-23}{new}
+%At least four digits, possible padded with zeros to make up four. The first
+%argument is the sign, then follow the ten digits.
+% \begin{macrocode}
+\def\@texosquery@at@leastfourdigits#1#2\@texosquery@end@atleastfourdigits{%
+ \@texosquery@fmtsign{#1}%
+ \ifnum#2<1000
+ \@texosquery@lastfouroften#2%
+ \else
+ \number#2
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@threedigitsexactly}
+%\changes{1.2}{2017-03-23}{new}
+%Exactly three digits.
+% \begin{macrocode}
+\def\@texosquery@threedigitsexactly#1{%
+ \@texosquery@threedigits@exactly#1\@texosquery@threedigits@exactly
+}%
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@threedigits@exactly}
+%\changes{1.2}{2017-03-23}{new}
+%Exactly three digits.
+% \begin{macrocode}
+\def\@texosquery@threedigits@exactly#1#2\@texosquery@threedigits@exactly{%
+ \@texosquery@fmtsign{#1}%
+ \@texosquery@lastthreeoften#2%
+}%
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@twodigitsexactly}
+%\changes{1.2}{2017-03-23}{new}
+%Exactly two digits.
+% \begin{macrocode}
+\def\@texosquery@twodigitsexactly#1{%
+ \@texosquery@twodigits@exactly#1\@texosquery@twodigits@exactly
+}%
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@twodigits@exactly}
+%\changes{1.2}{2017-03-23}{new}
+%Exactly two digits.
+% \begin{macrocode}
+\def\@texosquery@twodigits@exactly#1#2\@texosquery@twodigits@exactly{%
+ \@texosquery@fmtsign{#1}%
+ \@texosquery@lasttwooften#2%
+}%
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fmt@dtf}
+%\changes{1.2}{2017-03-23}{new}
+%\begin{definition}
+%\cs{@texosquery@fmt@dtf}\marg{n}\marg{designator}
+%\end{definition}
+%
+%When formatting a date-time pattern \cs{texosquerydtf} will
+%temporarily be redefined to this command. This command indicates
+%the format obtained by \meta{n} instances of \meta{designator}.
+%For example, \verb|{2}{M}| indicates the format \texttt{MM}. This
+%command tests for \cs{texosqueryfmtpat}\meta{format}, which should
+%take a single argument. If defined, that's used, otherwise use one
+%of the numeric commands defined above. The \sty{locale} package
+%defines \cs{texosqueryfmtpatMMM} and so on to use the locale's month names
+%etc.
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fmt@dtf#1#2{%
+ \@texosquery@ifundef{@texosquery@fmt@#2}%
+ {\@texosquery@warn{Unknown date-time pattern designator `#2'}}%
+ {%
+ \ifcase#1
+ \or
+ \@texosquery@ifundef{texosqueryfmtpat#2}%
+ {%
+ \expandafter\expandafter\expandafter
+ \@texosquery@atleastonedigit
+ \expandafter\expandafter\expandafter
+ {\csname @texosquery@fmt@#2\endcsname}%
+ }%
+ {%
+ \csname texosqueryfmtpat#2\expandafter\expandafter\expandafter\endcsname
+ \expandafter\expandafter\expandafter
+ {\csname @texosquery@fmt@#2\endcsname}%
+ }%
+ \or
+ \@texosquery@ifundef{texosqueryfmtpat#2#2}%
+ {%
+ \expandafter\expandafter\expandafter
+ \@texosquery@twodigitsexactly
+ \expandafter\expandafter\expandafter
+ {\csname @texosquery@fmt@#2\endcsname}%
+ }%
+ {%
+ \csname texosqueryfmtpat#2#2\expandafter\expandafter\expandafter
+ \endcsname \expandafter\expandafter\expandafter
+ {\csname @texosquery@fmt@#2\endcsname}%
+ }%
+ \or
+ \@texosquery@ifundef{texosqueryfmtpat#2#2#2}%
+ {%
+ \expandafter\expandafter\expandafter
+ \@texosquery@threedigitsexactly
+ \expandafter\expandafter\expandafter
+ {\csname @texosquery@fmt@#2\endcsname}%
+ }%
+ {%
+ \csname texosqueryfmtpat#2#2#2\expandafter\expandafter\expandafter
+ \endcsname \expandafter\expandafter\expandafter
+ {\csname @texosquery@fmt@#2\endcsname}%
+ }%
+ \else
+ \@texosquery@ifundef{texosqueryfmtpat#2#2#2#2}%
+ {%
+ \expandafter\expandafter\expandafter
+ \@texosquery@atleastfourdigits
+ \expandafter\expandafter\expandafter
+ {\csname @texosquery@fmt@#2\endcsname}%
+ }%
+ {%
+ \csname texosqueryfmtpat#2#2#2#2\expandafter\expandafter\expandafter
+ \endcsname \expandafter\expandafter\expandafter
+ {\csname @texosquery@fmt@#2\endcsname}%
+ }%
+ \fi
+ }%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%Provide default commands for the time zone designators, since the
+%time zone isn't supplied as a single integer.
+%
+%\begin{macro}{\texosqueryfmttimezonehr}
+%\changes{1.2}{2017-03-23}{new}
+%Allow for \verb|-0| so append 1 to hour in test.
+% \begin{macrocode}
+\def\texosqueryfmttimezonehr#1{%
+ \ifnum#11<0\@texosquery@fmtminus
+ \ifnum#1>-10 0\fi\number-#1
+ \else
+ +\ifnum#1<10 0\fi\number#1
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmttimezonenumhr}
+%\changes{1.2}{2017-03-23}{new}
+%Like the above, but don't zero-pad or prefix with plus sign.
+% \begin{macrocode}
+\def\texosqueryfmttimezonenumhr#1{%
+ \ifnum#11<0\@texosquery@fmtminus
+ \number-#1
+ \else
+ \number#1
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmttimezonemin}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosqueryfmttimezonemin#1{%
+ \ifnum#1<10 0\fi\number#1
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@firstoffour}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@firstoffour#1#2#3#4{#1}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@secondoffour}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@secondoffour#1#2#3#4{#2}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@thirdoffour}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@thirdoffour#1#2#3#4{#3}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@fourthoffour}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@fourthoffour#1#2#3#4{#4}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryshorttimezone}
+%\changes{1.2}{2017-03-23}{new}
+%Maps id to short time zone display name. This will need redefining
+%as appropriate. The default simply expands to the ID.
+%Mappings can be obtained for a particular locale using the
+%\shortarg{Z} or \longarg{time-zones} action.
+% \begin{macrocode}
+\def\texosqueryshorttimezone#1{#1}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryshortdstzone}
+%\changes{1.2}{2017-03-23}{new}
+%Maps id to short daylight saving time zone display name. This will need redefining
+%as appropriate. The default simply expands to the ID followed by
+%(DST).
+% \begin{macrocode}
+\def\texosqueryshortdstzone#1{#1 (DST)}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerylongtimezone}
+%\changes{1.2}{2017-03-23}{new}
+%Maps id to long time zone display name. This will need redefining
+%as appropriate. The default simply expands to the ID.
+% \begin{macrocode}
+\def\texosquerylongtimezone#1{#1}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerylongdstzone}
+%\changes{1.2}{2017-03-23}{new}
+%Maps id to long daylight saving time zone display name. This will need redefining
+%as appropriate. The default simply expands to the ID followed by
+%(DST).
+% \begin{macrocode}
+\def\texosquerylongdstzone#1{#1 (DST)}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerytimesep}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerytimesep{:}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatz}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{z} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatz#1{%
+ \expandafter\ifnum\@texosquery@fourthoffour#1=0
+ \expandafter\texosqueryshorttimezone\expandafter{\@texosquery@thirdoffour#1}%
+ \else
+ \expandafter\texosqueryshortdstzone\expandafter{\@texosquery@thirdoffour#1}%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatzz}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{zz} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatzz#1{%
+ \expandafter\ifnum\@texosquery@fourthoffour#1=0
+ \expandafter\texosqueryshorttimezone\expandafter{\@texosquery@thirdoffour#1}%
+ \else
+ \expandafter\texosqueryshortdstzone\expandafter{\@texosquery@thirdoffour#1}%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatzzz}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{zzz} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatzzz#1{%
+ \expandafter\ifnum\@texosquery@fourthoffour#1=0
+ \expandafter\texosquerylongtimezone\expandafter{\@texosquery@thirdoffour#1}%
+ \else
+ \expandafter\texosquerylongdstzone\expandafter{\@texosquery@thirdoffour#1}%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatzzzz}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{zzzz} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatzzzz#1{%
+ \expandafter\ifnum\@texosquery@fourthoffour#1=0
+ \expandafter\texosquerylongtimezone\expandafter{\@texosquery@thirdoffour#1}%
+ \else
+ \expandafter\texosquerylongdstzone\expandafter{\@texosquery@thirdoffour#1}%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatZ}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{Z} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatZ#1{%
+ \expandafter\texosqueryfmttimezonehr\expandafter
+ {\@texosquery@firstoffour#1}%
+ \expandafter\texosqueryfmttimezonemin\expandafter
+ {\@texosquery@secondoffour#1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatZZ}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{ZZ} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatZZ#1{%
+ \expandafter\texosqueryfmttimezonehr\expandafter
+ {\@texosquery@firstoffour#1}%
+ \expandafter\texosqueryfmttimezonemin\expandafter
+ {\@texosquery@secondoffour#1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatZZZ}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{ZZZ} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatZZZ#1{%
+ \expandafter\texosqueryfmttimezonehr\expandafter
+ {\@texosquery@firstoffour#1}%
+ \expandafter\texosqueryfmttimezonemin\expandafter
+ {\@texosquery@secondoffour#1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatZZZZ}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{ZZZZ} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatZZZZ#1{%
+ \expandafter\texosqueryfmttimezonehr\expandafter
+ {\@texosquery@firstoffour#1}%
+ \expandafter\texosqueryfmttimezonemin\expandafter
+ {\@texosquery@secondoffour#1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatX}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{X} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatX#1{%
+ \expandafter\texosqueryfmttimezonehr\expandafter
+ {\@texosquery@firstoffour#1}%
+ \texosquerytimesep
+ \expandafter\texosqueryfmttimezonemin\expandafter
+ {\@texosquery@secondoffour#1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatXX}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{XX} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatXX#1{%
+ \expandafter\texosqueryfmttimezonehr\expandafter
+ {\@texosquery@firstoffour#1}%
+ \texosquerytimesep
+ \expandafter\texosqueryfmttimezonemin\expandafter
+ {\@texosquery@secondoffour#1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatXXX}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{XXX} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatXXX#1{%
+ \expandafter\texosqueryfmttimezonehr\expandafter
+ {\@texosquery@firstoffour#1}%
+ \texosquerytimesep
+ \expandafter\texosqueryfmttimezonemin\expandafter
+ {\@texosquery@secondoffour#1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatXXXX}
+%\changes{1.2}{2017-03-23}{new}
+%Default time zone format for \texttt{XXXX} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatXXXX#1{%
+ \expandafter\texosqueryfmttimezonehr\expandafter
+ {\@texosquery@firstoffour#1}%
+ \texosquerytimesep
+ \expandafter\texosqueryfmttimezonemin\expandafter
+ {\@texosquery@secondoffour#1}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpata}
+%\changes{1.2}{2017-03-23}{new}
+%Default am/pm designator for the \texttt{a} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpata#1{%
+ \ifnum#1=0 AM\else PM\fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpataa}
+%\changes{1.2}{2017-03-23}{new}
+%Default am/pm for the \texttt{aa} designator. Just make it the same
+%as the \texttt{a} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpataa{\texosqueryfmtpata}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpataaa}
+%\changes{1.2}{2017-03-23}{new}
+%Default am/pm for the \texttt{aaa} designator. Just make it the same
+%as the \texttt{a} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpataaa{\texosqueryfmtpata}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpataaaa}
+%\changes{1.2}{2017-03-23}{new}
+%Default am/pm for the \texttt{aaaa} designator. Just make it the same
+%as the \texttt{a} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpataaaa{\texosqueryfmtpata}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatG}
+%\changes{1.2}{2017-03-23}{new}
+%Default era designator for the \texttt{G} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatG#1{%
+ \ifnum#1=1 AD\else BC\fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatGG}
+%\changes{1.2}{2017-03-23}{new}
+%Default era for the \texttt{GG} designator. Just make it the same
+%as the \texttt{G} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatGG{\texosqueryfmtpatG}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatGGG}
+%\changes{1.2}{2017-03-23}{new}
+%Default era for the \texttt{GGG} designator. Just make it the same
+%as the \texttt{G} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatGGG{\texosqueryfmtpatG}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosqueryfmtpatGGGG}
+%\changes{1.2}{2017-03-23}{new}
+%Default era for the \texttt{GGGG} designator. Just make it the same
+%as the \texttt{G} designator.
+% \begin{macrocode}
+\def\texosqueryfmtpatGGGG{\texosqueryfmtpatG}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\subsubsection{Applying Numeric Patterns}
+%\label{sec:applynumpatterns}
+%
+%\begin{macro}{\texosqueryfmtnumber}
+%\changes{1.2}{2017-03-23}{new}
+%\begin{definition}
+%\cs{texosqueryfmtnumber}\marg{pattern}\marg{int}\marg{frac}\marg{mantissa}
+%\end{definition}
+%General purpose low-level number formatting command. The first argument
+%\meta{pattern} is the pattern. The other arguments are unformatted integers
+%and must be present and not exceed 10 digits each. The \meta{frac} part must
+%not start with a sign. The minus sign should go at the start of
+%\meta{int} for negative numbers. The plus sign is
+%optional for positive \meta{int} or \meta{mantissa} and not
+%permitted in \meta{frac}. The arguments may each be the actual numerical
+%value or be a single control sequence whose replacement text is
+%the value. Avoid anything more complicated than that.
+%
+%This package doesn't provide a higher level command that can split a number
+%into integer, fractional and mantissa parts.
+% \begin{macrocode}
+\def\texosqueryfmtnumber#1#2#3#4{%
+ \begingroup
+ \let\texosquerypatstr\texosquerypatfmtstr
+ \let\texosquerypatquote\texosquerypatfmtquote
+ \let\texosquerypatplusminus\texosquerypatfmt@plusminus
+ \let\texosquerypatnum\texosquerypatfmt@num
+ \let\texosquerypatsinum\texosquerypatfmt@sinum
+ \let\texosquerypatdec\texosquerypatfmt@dec
+ \let\texosquerypatprefixcurrency\texosquery@patfmt@prefixcurrency
+ \let\texosquerypatprefixicurrency\texosquery@patfmt@prefixicurrency
+ \let\texosquerypatsuffixcurrency\texosquery@patfmt@suffixcurrency
+ \let\texosquerypatsuffixicurrency\texosquery@patfmt@suffixicurrency
+ \let\texosquerypatdigit\texosquerypatfmt@digit
+ \let\texosquerypatdigitnozero\texosquerypatfmt@digitnozero
+ \let\texosquerypatgroupsep\texosquerypatfmt@groupsep
+ \let\texosquerypatprefixpercent\texosquery@patfmt@prefixpercent
+ \let\texosquerypatsuffixpercent\texosquery@patfmt@suffixpercent
+ \let\texosquerypatprefixpermill\texosquery@patfmt@prefixpermill
+ \let\texosquerypatsuffixpermill\texosquery@patfmt@suffixpermill
+ \let\texosquerypatminus\@texosquerypat@numfmt@sign
+ \let\texosquerypatfmt@decsep\texosquerypatfmtdecsep
+% \end{macrocode}
+%Allow for negative zero in the \meta{int} part. To avoid overflow,
+%first check for 0 and then append 1 to the number to
+%catch \verb|-0|.
+% \begin{macrocode}
+ \edef\@texosquery@sgn{%
+ \ifnum#2=0
+ \expandafter\ifnum#21<0 -\else+\fi
+ \else
+ \ifnum#2<0 -\else+\fi
+ \fi
+ }%
+% \end{macrocode}
+%Allow for arguments passed as control sequences that expand to a
+%number.
+% \begin{macrocode}
+ \edef\@texosquery@int{\expandafter\@texosquery@paddigits
+ \expandafter{\number#2}}%
+ \let\@texosquery@si@int\@texosquery@int
+% \end{macrocode}
+%Can't use \cs{number} here as we'll lose any leading zeros.
+% \begin{macrocode}
+ \edef\@texosquery@frac{\expandafter\@texosquery@paddigits@trailing
+ \expandafter{#3}}%
+ \let\@texosquery@si@frac\@texosquery@frac
+ \edef\@texosquery@mantissa{\expandafter\@texosquery@paddigits
+ \expandafter{\number#4}}%
+% \end{macrocode}
+%Is the mantissa non-zero?
+% \begin{macrocode}
+ \ifnum#4=0\relax
+ \else
+ \expandafter\ifx\@texosquery@sgn-%
+ \edef\@texosquery@int{\expandafter
+ \@texosquery@paddigits@pos\expandafter{\number-#2}}%
+ \else
+ \edef\@texosquery@int{\@texosquery@paddigits@pos{#2}}%
+ \fi
+% \end{macrocode}
+%Shift.
+% \begin{macrocode}
+ \ifnum#4<0
+ \expandafter\@texosquery@neg@shift\expandafter{\number-#4}%
+ \else
+ \@texosquery@pos@shift{#4}%
+ \fi
+ \expandafter\ifx\@texosquery@sgn-%
+ \edef\@texosquery@int{\expandafter\@texosquery@paddigits
+ \expandafter{\number-\@texosquery@int}}%
+ \else
+ \edef\@texosquery@int{\expandafter\@texosquery@paddigits
+ \expandafter{\number\@texosquery@int}}%
+ \fi
+ \edef\@texosquery@frac{\@texosquery@paddigits@trailing{\@texosquery@frac}}%
+ \fi
+ \edef\@texosquery@current{\expandafter\@texosquery@gobble\@texosquery@int}%
+ \let\@texosquery@zerodigit\@texosquery@zerodigit@leading
+ \expandafter\ifx\@texosquery@sgn-%
+ \let\@texosquery@currentsign\texosquerypatfmtminus
+ \else
+ \let\@texosquery@currentsign\texosquerypatfmtplus
+ \fi
+ \@texosquery@digitindex=0\relax
+ \let\@texosquery@patfmt@dosep\empty
+ \@texosquery@digitfoundfalse
+ #1%
+ \endgroup
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@digitindex}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\newcount\@texosquery@digitindex
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\if@texosquery@digitfound}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\newif\if@texosquery@digitfound
+% \end{macrocode}
+%\end{macro}
+%
+%Macros to shift the decimal place.
+%\begin{macro}{\@texosquery@pos@shift}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@pos@shift#1{%
+ \ifcase#1
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@lastnineoften\@texosquery@int
+ \expandafter\@texosquery@firstoften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastnineoften\@texosquery@frac
+ }%
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@lasteightoften\@texosquery@int
+ \expandafter\@texosquery@firsttwooften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lasteightoften\@texosquery@frac
+ }%
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@lastsevenoften\@texosquery@int
+ \expandafter\@texosquery@firstthreeoften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastsevenoften\@texosquery@frac
+ }%
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@lastsixoften\@texosquery@int
+ \expandafter\@texosquery@firstfouroften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastsixoften\@texosquery@frac
+ }%
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@lastfiveoften\@texosquery@int
+ \expandafter\@texosquery@firstfiveoften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastfiveoften\@texosquery@frac
+ }%
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@lastfouroften\@texosquery@int
+ \expandafter\@texosquery@firstsixoften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastfouroften\@texosquery@frac
+ }%
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@lastthreeoften\@texosquery@int
+ \expandafter\@texosquery@firstsevenoften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastthreeoften\@texosquery@frac
+ }%
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@lasttwooften\@texosquery@int
+ \expandafter\@texosquery@firsteightoften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lasttwooften\@texosquery@frac
+ }%
+ \or
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@tenthoften\@texosquery@int
+ \expandafter\@texosquery@firstnineoften\@texosquery@frac}%
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@tenthoften\@texosquery@frac
+ }%
+ \or
+ \let\@texosquery@int\@texosquery@frac
+ \edef\@texosquery@frac{0}%
+% \end{macrocode}
+%Anything larger will require scientific notation. Hopefully the
+%pattern supports this.
+% \begin{macrocode}
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@neg@shift}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@neg@shift#1{%
+ \ifcase#1
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastoneoften\@texosquery@int
+ \expandafter\@texosquery@firstnineoften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firstnineoften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lasttwooften\@texosquery@int
+ \expandafter\@texosquery@firsteightoften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firsteightoften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastthreeoften\@texosquery@int
+ \expandafter\@texosquery@firstsevenoften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firstsevenoften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastfouroften\@texosquery@int
+ \expandafter\@texosquery@firstsixoften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firstsixoften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastfiveoften\@texosquery@int
+ \expandafter\@texosquery@firstfiveoften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firstfiveoften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastsixoften\@texosquery@int
+ \expandafter\@texosquery@firstfouroften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firstfouroften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastsevenoften\@texosquery@int
+ \expandafter\@texosquery@firstthreeoften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firstthreeoften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lasteightoften\@texosquery@int
+ \expandafter\@texosquery@firsttwooften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firsttwooften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{%
+ \expandafter\@texosquery@lastnineoften\@texosquery@int
+ \expandafter\@texosquery@firstoften\@texosquery@frac
+ }%
+ \edef\@texosquery@int{%
+ \expandafter\@texosquery@firstoften\@texosquery@int
+ }%
+ \or
+ \edef\@texosquery@frac{\@texosquery@int\@texosquery@frac}%
+ \edef\@texosquery@int{0}%
+% \end{macrocode}
+%Anything beyond this will require scientific notation. Hopefully the
+%pattern supports it.
+% \begin{macrocode}
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquerypat@numfmt@sign}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquerypat@numfmt@sign{%
+ \@texosquery@currentsign
+ \let\@texosquery@currentsign\empty
+}
+% \end{macrocode}
+%\end{macro}
+%
+%
+%\begin{macro}{\texosquerypatfmtstr}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmtstr#1{#1}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmtquote}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmtquote{'}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmt@plusminus}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmt@plusminus#1#2{%
+ \edef\@texosquery@current{\expandafter\@texosquery@gobble\@texosquery@int}%
+ \@texosquery@digitindex=0\relax
+ \let\@texosquery@patfmt@dosep\empty
+ \@texosquery@digitfoundfalse
+ \expandafter\ifx\@texosquery@sgn-%
+ #2%
+ \ifnum\@texosquery@digitindex=10
+ \else
+ \@texosquery@invalidpattern{#2}%
+ \fi
+ \else
+ #1%
+ \ifnum\@texosquery@digitindex=10
+ \else
+ \@texosquery@invalidpattern{#1}%
+ \fi
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmt@num}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmt@num#1{#1}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmtexp}
+%\changes{1.2}{2017-03-23}{new}
+%Exponent symbol. Change as appropriate.
+% \begin{macrocode}
+\def\texosquerypatfmtexp{E}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypatfmt@sinum}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmt@sinum#1#2{%
+ \let\@texosquery@int\@texosquery@si@int
+ \let\@texosquery@frac\@texosquery@si@frac
+ \let\@texosquery@current\@texosquery@int
+ #1%
+ \texosquerypatfmtexp
+ {\let\@texosquery@zerodigit\@texosquery@zerodigit@leading
+ \ifnum\@texosquery@mantissa<0\relax
+ \let\@texosquery@currentsign\texosquerypatfmtminus
+ \else
+ \let\@texosquery@currentsign\texosquerypatfmtplus
+ \fi
+ \edef\@texosquery@current{\expandafter\@texosquery@gobble\@texosquery@mantissa}%
+ \@texosquery@digitindex=0\relax
+ \let\@texosquery@patfmt@dosep\empty
+ \@texosquery@digitfoundfalse
+ #2}}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmtdecsep}
+%\changes{1.2}{2017-03-23}{new}
+%Decimal separator. Change as appropriate.
+% \begin{macrocode}
+\def\texosquerypatfmtdecsep{.}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypatfmtcurdecsep}
+%\changes{1.2}{2017-03-23}{new}
+%Currency decimal separator. Change as appropriate.
+% \begin{macrocode}
+\def\texosquerypatfmtcurdecsep{.}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypatfmt@dec}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmt@dec#1#2{%
+ \edef\@texosquery@current{\expandafter\@texosquery@gobble\@texosquery@int}%
+ \@texosquery@digitindex=0\relax
+ \let\@texosquery@patfmt@dosep\empty
+ \@texosquery@digitfoundfalse
+ \let\@texosquery@zerodigit\@texosquery@zerodigit@leading
+ #1%
+ \ifnum\@texosquery@digitindex=10
+ \else
+ \@texosquery@invalidpattern{#1}%
+ \fi
+% \end{macrocode}
+%\changes{1.2.1}{2017-03-28}{moved decimal sep}
+%We can't display the decimal separator just yet as the pattern may
+%not show any digits for the fractional part. So only do the
+%separator just before the first digit.
+% \begin{macrocode}
+ \let\@texosquery@patfmt@dosep\texosquerypatfmt@decsep
+ \let\@texosquery@current\@texosquery@frac
+ \@texosquery@digitindex=0\relax
+ \@texosquery@digitfoundfalse
+ \let\@texosquery@zerodigit\@texosquery@zerodigit@trailing
+ \let\@texosquery@currentsign\empty
+ #2%
+ \ifnum\@texosquery@digitindex=10
+ \else
+ \@texosquery@invalidpattern{#2}%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmtint}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmtint#1{%
+ \edef\@texosquery@current{\expandafter\@texosquery@gobble\@texosquery@int}%
+ \@texosquery@digitindex=0\relax
+ \@texosquery@digitfoundfalse
+ \let\@texosquery@zerodigit\@texosquery@zerodigit@leading
+ #1%
+ \ifnum\@texosquery@digitindex=10
+ \else
+ \@texosquery@invalidpattern{#1}%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@setpatdisplay}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@setpatdisplay{%
+ \def\texosquerypatstr##1{'##1'}%
+ \def\texosquerypatquote{''}%
+ \def\texosquerypatplusminus##1##2{##1;##2}%
+ \def\texosquerypatnum##1{##1}%
+ \def\texosquerypatsinum##1##2{##1E##2}%
+ \def\texosquerypatdec##1##2{##1.##2}%
+ \def\texosquerypatprefixcurrency##1##2{##2¤##1}%
+ \def\texosquerypatprefixicurrency##1##2{##2¤¤##1}%
+ \def\texosquerypatsuffixcurrency##1##2{##1¤##2}%
+ \def\texosquerypatsuffixicurrency##1##2{##1¤¤##2}%
+ \def\texosquerypatdigit{0}%
+ \def\texosquerypatdigitnozero{\#}%
+ \def\texosquerypatminus{-}%
+ \def\texosquerypatgroupsep{,}%
+ \def\texosquerypatprefixpercent##1##2{##2\%##1}%
+ \def\texosquerypatsuffixpercent##1##2{##1\%##2}%
+ \def\texosquerypatprefixpermill##1##2{##2‰##1}%
+ \def\texosquerypatsuffixpermill##1##2{##1‰##2}%
+ \def\texosquerypatfmt@decsep{.}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@invalidpattern}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@invalidpattern#1{%
+ \begingroup
+ \@texosquery@setpatdisplay
+ \@texosquery@err{10 digit specifiers expected in
+ numeric pattern #1. Found \number\@texosquery@digitindex}%
+ {Each integer element of a numeric pattern must have exactly
+ 10 digit specifiers (0 or \#)}%
+ \endgroup
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypatfmtcurrencysign}
+%\changes{1.2}{2017-03-23}{new}
+%Currency symbol. Redefine as appropriate.
+% \begin{macrocode}
+\def\texosquerypatfmtcurrencysign{\$}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypatfmticurrencysign}
+%\changes{1.2}{2017-03-23}{new}
+%International currency symbol. There's no generic fallback that's
+%independent of the input encoding, so this uses a UTF-8 character
+%on the assumption that if \cs{textcurrency} isn't available (for
+%example, through \sty{textcomp}, then the user may be using \XeTeX\
+%or \LuaTeX). If this isn't the case, and there's no UTF-8 support,
+%then this command will need to be redefined as appropriate.
+% \begin{macrocode}
+\ifx\textcurrency\undefined
+ \def\texosquerypatfmticurrencysign{¤}
+\else
+ \def\texosquerypatfmticurrencysign{\textcurrency}
+\fi
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquery@patfmt@prefixcurrency}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquery@patfmt@prefixcurrency#1#2{%
+ \let\texosquerypatfmt@decsep\texosquerypatfmtcurdecsep
+ #2\texosquerypatfmtcurrencysign#1%
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquery@patfmt@prefixicurrency}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquery@patfmt@prefixicurrency#1#2{%
+ \let\texosquerypatfmt@decsep\texosquerypatfmtcurdecsep
+ #2\texosquerypatfmticurrencysign#1%
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquery@patfmt@suffixcurrency}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquery@patfmt@suffixcurrency#1#2{%
+ \let\texosquerypatfmt@decsep\texosquerypatfmtcurdecsep
+ #1\texosquerypatfmtcurrencysign#2%
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquery@patfmt@suffixicurrency}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquery@patfmt@suffixicurrency#1#2{%
+ \let\texosquerypatfmt@decsep\texosquerypatfmtcurdecsep
+ #1\texosquerypatfmticurrencysign#2%
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmt@digit}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmt@digit{%
+ \advance\@texosquery@digitindex by 1\relax
+ \if@texosquery@digitfound
+ \else
+ \ifx\@texosquery@currentsign\texosquerypatfmtminus
+ \texosquerypatfmtminus
+ \let\@texosquery@currentsign\empty
+ \else
+ \@texosquery@patfmt@dosep
+ \let\@texosquery@patfmt@dosep\empty
+ \fi
+ \fi
+ \@texosquery@digitfoundtrue
+ \ifcase\@texosquery@digitindex
+ \or
+ \expandafter\@texosquery@firstoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@secondoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@thirdoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@fourthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@fifthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@sixthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@seventhoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@eighthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@ninthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@tenthoften\@texosquery@current
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmt@digitnozero}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmt@digitnozero{%
+ \advance\@texosquery@digitindex by 1\relax
+ \edef\@texosquery@digit{%
+ \ifcase\@texosquery@digitindex
+ 0%
+ \or
+ \expandafter\@texosquery@firstoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@secondoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@thirdoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@fourthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@fifthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@sixthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@seventhoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@eighthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@ninthoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@tenthoften\@texosquery@current
+ \else
+ 0%
+ \fi
+ }%
+ \ifnum\@texosquery@digit=0\relax
+ \@texosquery@zerodigit
+ \else
+ \if@texosquery@digitfound
+ \else
+ \ifx\@texosquery@currentsign\texosquerypatfmtminus
+ \texosquerypatfmtminus
+ \let\@texosquery@currentsign\empty
+ \else
+ \@texosquery@patfmt@dosep
+ \let\@texosquery@patfmt@dosep\empty
+ \fi
+ \fi
+ \@texosquery@digitfoundtrue
+ \@texosquery@digit
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@zerodigit@leading}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@zerodigit@leading{%
+ \edef\@texosquery@digit{%
+ \ifcase\@texosquery@digitindex
+ 0%
+ \or
+ \expandafter\@texosquery@firstoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@firsttwooften\@texosquery@current
+ \or
+ \expandafter\@texosquery@firstthreeoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@firstfouroften\@texosquery@current
+ \or
+ \expandafter\@texosquery@firstfiveoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@firstsixoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@firstsevenoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@firsteightoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@firstnineoften\@texosquery@current
+ \or
+ \@texosquery@current
+ \else
+ 0%
+ \fi
+ }%
+ \ifnum\@texosquery@digit>0\relax
+ \if@texosquery@digitfound
+ \else
+ \ifx\texosquerypatminus\texosquerypatfmtminus
+ \texosquerypatfmtminus
+ \else
+ \@texosquery@patfmt@dosep
+ \let\@texosquery@patfmt@dosep\empty
+ \fi
+ \fi
+ \@texosquery@digitfoundtrue
+ 0%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@zerodigit@trailing}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@zerodigit@trailing{%
+ \edef\@texosquery@digit{%
+ \ifcase\@texosquery@digitindex
+ 0%
+ \or
+ \@texosquery@current
+ \or
+ \expandafter\@texosquery@lastnineoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@lasteightoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@lastsevenoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@lastsixoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@lastfiveoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@lastfouroften\@texosquery@current
+ \or
+ \expandafter\@texosquery@lastthreeoften\@texosquery@current
+ \or
+ \expandafter\@texosquery@lasttwooften\@texosquery@current
+ \or
+ \expandafter\@texosquery@tenthoften\@texosquery@current
+ \else
+ 0%
+ \fi
+ }%
+ \ifnum\@texosquery@digit>0\relax
+ \if@texosquery@digitfound
+ \else
+ \ifx\texosquerypatminus\texosquerypatfmtminus
+ \texosquerypatfmtminus
+ \else
+ \@texosquery@patfmt@dosep
+ \let\@texosquery@patfmt@dosep\empty
+ \fi
+ \fi
+ \@texosquery@digitfoundtrue
+ 0%
+ \fi
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypatfmtminus}
+%\changes{1.2}{2017-03-23}{new}
+%Formatted minus sign. Redefined as appropriate.
+% \begin{macrocode}
+\def\texosquerypatfmtminus{\ifmmode-\else$-$\fi}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmtplus}
+%\changes{1.2}{2017-03-23}{new}
+%Formatted plus sign. Redefined as appropriate.
+% \begin{macrocode}
+\def\texosquerypatfmtplus{\ifmmode+\else$+$\fi}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmtgroupsep}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmtgroupsep{,}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquerypatfmt@groupsep}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquerypatfmt@groupsep{%
+ \if@texosquery@digitfound\texosquerypatfmtgroupsep\fi}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypatfmtpercentsign}
+%\changes{1.2}{2017-03-23}{new}
+%Percent sign used in number format.
+% \begin{macrocode}
+\def\texosquerypatfmtpercentsign{\%}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquerypatfmtpermillsign}
+%\changes{1.2}{2017-03-23}{new}
+%Per-mill sign used in number format. Redefine as appropriate.
+% \begin{macrocode}
+\def\texosquerypatfmtpermillsign{‰}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\@texosquery@adjust@per}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\@texosquery@adjust@per#1{%
+ \@texosquery@pos@shift{#1}%
+ \edef\@texosquery@int{\expandafter\@texosquery@paddigits
+ \expandafter{\number\@texosquery@int}}%
+ \edef\@texosquery@frac{\@texosquery@paddigits@trailing{\@texosquery@frac}}%
+ \edef\@texosquery@current{\expandafter\@texosquery@gobble\@texosquery@int}%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%\begin{macro}{\texosquery@patfmt@prefixpercent}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquery@patfmt@prefixpercent#1#2{%
+ \@texosquery@adjust@per{2}%
+ #2\texosquerypatfmtpercentsign#1%
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquery@patfmt@suffixpercent}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquery@patfmt@suffixpercent#1#2{%
+ \@texosquery@adjust@per{2}%
+ #1\texosquerypatfmtpercentsign#2%
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquery@patfmt@prefixpermill}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquery@patfmt@prefixpermill#1#2{%
+ \@texosquery@adjust@per{3}%
+ #2\texosquerypatfmtpermillsign#1%
+}
+% \end{macrocode}
+%\end{macro}
+%\begin{macro}{\texosquery@patfmt@suffixpermill}
+%\changes{1.2}{2017-03-23}{new}
+% \begin{macrocode}
+\def\texosquery@patfmt@suffixpermill#1#2{%
+ \@texosquery@adjust@per{3}%
+ #1\texosquerypatfmtpermillsign#2%
+}
+% \end{macrocode}
+%\end{macro}
+%
+%All done.
+%Restore the category code of \texttt{@}:
+% \begin{macrocode}
+\@texosquery@restore@at
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery.tex>
+% \end{macrocode}
+%\fi
+%
+%\subsection{\LaTeX\ Code}
+%This is just a simple wrapper for \file{texosquery.tex}
+%so that it can be loaded using \LaTeX's standard \cs{usepackage}
+%method.
+%\iffalse
+% \begin{macrocode}
+%<*texosquery.sty>
+% \end{macrocode}
+%\fi
+%Identify package:
+% \begin{macrocode}
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{texosquery}[2017/06/20 v1.6 (NLCT)]
+% \end{macrocode}
+%Load \file{texosquery.tex}:
+% \begin{macrocode}
+\input{texosquery}
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery.sty>
+% \end{macrocode}
+%\fi
+%
+%\subsection{Configuration File (\file{texosquery.cfg})}
+%The configuration file. This will need to be edited as appropriate
+%to the system.
+%\iffalse
+% \begin{macrocode}
+%<*texosquery.cfg>
+% \end{macrocode}
+%\fi
+% \begin{macrocode}
+%<<COMMENT
+% This configuration file must be edited to match your system.
+% Copy the file to your TEXMFLOCAL or TEXMFHOME tree to prevent it
+% from being overwritten by updates.
+
+% 1. Check your version of Java. To do this run
+% java -version
+% from your command line.
+
+% * If the version number starts with "1.5" or "1.6" then
+% \TeXOSInvokerName must be texosquery-jre5
+%
+% * If the version number starts with "1.7" then
+% \TeXOSInvokerName must be texosquery
+%
+% * If the version number starts with "1.8" then
+% \TeXOSInvokerName must be texosquery-jre8
+
+% (bash users need to check that the .sh extension has been removed
+% from the bash scripts.)
+
+\def\TeXOSInvokerName{texosquery}
+
+% 2. If the invoker name given in the definition above is on the
+% restricted list, uncomment the line below to allow it to be run
+% in restricted mode:
+
+%\TeXOSQueryAllowRestricted
+
+% Leave it commented out if the invoker name is not on the
+% restricted list.
+%COMMENT
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery.cfg>
+% \end{macrocode}
+%\fi
+%\subsection{Bash Scripts}
+%These are the bash scripts for Unix-like systems.
+%The first line
+%\begin{verbatim}
+%#!/bin/sh
+%\end{verbatim}
+%is added when the files are extracted by \file{texosquery.ins}
+%(since \verb|\nopreamble| automatically inserts a blank line at the
+%start of the file).
+%\iffalse
+% \begin{macrocode}
+%<*texosquery.sh>
+% \end{macrocode}
+%\fi
+%\subsubsection{\file{texosquery.sh}}
+%This now has a check for cygwin.
+% \begin{macrocode}
+kernel=`uname -s`
+if test "${kernel#*CYGWIN}" != "$kernel"
+then
+ jarpath=`cygpath -w $(kpsewhich --progname=texosquery --format=texmfscripts texosquery.jar)`
+else
+ jarpath=`kpsewhich --progname=texosquery --format=texmfscripts texosquery.jar`
+fi
+java -jar "$jarpath" "$@"
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery.sh>
+% \end{macrocode}
+%\fi
+%
+%\iffalse
+% \begin{macrocode}
+%<*texosquery-jre8.sh>
+% \end{macrocode}
+%\fi
+%\subsubsection{\file{texosquery-jre8.sh}}
+% \begin{macrocode}
+kernel=`uname -s`
+if test "${kernel#*CYGWIN}" != "$kernel"
+then
+ jarpath=`cygpath -w $(kpsewhich --progname=texosquery --format=texmfscripts texosquery-jre8.jar)`
+else
+ jarpath=`kpsewhich --progname=texosquery --format=texmfscripts texosquery-jre8.jar`
+fi
+java -Djava.locale.providers=CLDR,JRE -jar "$jarpath" "$@"
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery-jre8.sh>
+% \end{macrocode}
+%\fi
+%
+%\iffalse
+% \begin{macrocode}
+%<*texosquery-jre5.sh>
+% \end{macrocode}
+%\fi
+%\subsubsection{\file{texosquery-jre5.sh}}
+% \begin{macrocode}
+kernel=`uname -s`
+if test "${kernel#*CYGWIN}" != "$kernel"
+then
+ jarpath=`cygpath -w $(kpsewhich --progname=texosquery --format=texmfscripts texosquery-jre5.jar)`
+else
+ jarpath=`kpsewhich --progname=texosquery --format=texmfscripts texosquery-jre5.jar`
+fi
+java -jar "$jarpath" "$@"
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery-jre5.sh>
+% \end{macrocode}
+%\fi
+%
+%\subsection{Windows Batch Scripts}
+%These are the batch scripts for Windows. \TeX\ on Windows doesn't
+%allow the creation of \file{.bat} files, so \file{.ins} file
+%creates these with the extension \file{.batch} which will need to
+%be changed to \file{.bat} after extraction.
+%\iffalse
+% \begin{macrocode}
+%<*texosquery.bat>
+% \end{macrocode}
+%\fi
+%\subsubsection{\file{texosquery.bat}}
+% \begin{macrocode}
+@ECHO OFF
+FOR /F "tokens=*" %%I IN ('kpsewhich --progname=texosquery --format=texmfscripts texosquery.jar') DO SET JARPATH=%%I
+java -jar "%JARPATH%" %*
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery.bat>
+% \end{macrocode}
+%\fi
+%
+%\iffalse
+% \begin{macrocode}
+%<*texosquery-jre8.bat>
+% \end{macrocode}
+%\fi
+%\subsubsection{\file{texosquery-jre8.bat}}
+% \begin{macrocode}
+@ECHO OFF
+FOR /F "tokens=*" %%I IN ('kpsewhich --progname=texosquery --format=texmfscripts texosquery-jre8.jar') DO SET JARPATH=%%I
+java -Djava.locale.providers=CLDR,JRE -jar "%JARPATH%" %*
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery-jre8.bat>
+% \end{macrocode}
+%\fi
+%
+%\iffalse
+% \begin{macrocode}
+%<*texosquery-jre5.bat>
+% \end{macrocode}
+%\fi
+%\subsubsection{\file{texosquery-jre5.bat}}
+% \begin{macrocode}
+@ECHO OFF
+FOR /F "tokens=*" %%I IN ('kpsewhich --progname=texosquery --format=texmfscripts texosquery-jre5.jar') DO SET JARPATH=%%I
+java -jar "%JARPATH%" %*
+% \end{macrocode}
+%\iffalse
+% \begin{macrocode}
+%</texosquery-jre5.bat>
+% \end{macrocode}
+%\fi
+%
+%\Finale
+\endinput
diff --git a/support/texosquery/texosquery.ins b/support/texosquery/texosquery.ins
new file mode 100644
index 0000000000..47c6a92102
--- /dev/null
+++ b/support/texosquery/texosquery.ins
@@ -0,0 +1,98 @@
+\input docstrip
+
+\preamble
+ texosquery
+ Copyright 2017 Nicola Talbot
+
+ This work may be distributed and/or modified under the
+ conditions of the LaTeX Project Public License, either version 1.3
+ of this license of (at your option) any later version.
+ The latest version of this license is in
+ http://www.latex-project.org/lppl.txt
+ and version 1.3 or later is part of all distributions of LaTeX
+ version 2005/12/01 or later.
+
+ This work has the LPPL maintenance status `maintained'.
+
+ The Current Maintainer of this work is Nicola Talbot.
+
+ This work consists of the files texosquery.dtx and texosquery.ins
+ and the derived files texosquery.sty, texosquery.tex and
+ texosquery.cfg. Additionally, the Java applications texosquery.jar,
+ texosquery-jre5.jar, texosquery-jre8.jar, the bash scripts
+ texosquery.sh, texosquery-jre5.sh, texosquery-jre8.sh, and
+ the corresponding batch files.
+\endpreamble
+
+\declarepreamble\cfgpreamble
+
+Please follow the instructions below.
+\endpreamble
+
+%The following is to prevent a blank line from appearing
+%at the start of the bash scripts (which occurs when simply using
+%\nopreamble).
+
+\ifx\@gobble\undefined
+ \def\@gobble#1{}
+\fi
+\def\literalhash{\expandafter\@gobble\string\#}
+
+\def\bashpreamble{\literalhash !/bin/sh}
+
+\askforoverwritefalse
+
+\generate
+{%
+ \file{texosquery.sty}%
+ {%
+ \usepreamble\defaultpreamble
+ \usepostamble\defaultpostamble
+ \from{texosquery.dtx}{texosquery.sty,package}%
+ }
+ \file{texosquery.tex}%
+ {%
+ \usepreamble\defaultpreamble
+ \usepostamble\defaultpostamble
+ \from{texosquery.dtx}{texosquery.tex,package}%
+ }
+ \file{texosquery.cfg}%
+ {%
+ \usepreamble\cfgpreamble
+ \usepostamble\defaultpostamble
+ \from{texosquery.dtx}{texosquery.cfg}%
+ }
+ \file{texosquery.sh}%
+ {%
+ \usepreamble\bashpreamble\nopostamble
+ \from{texosquery.dtx}{texosquery.sh}%
+ }
+ \file{texosquery-jre5.sh}%
+ {%
+ \usepreamble\bashpreamble\nopostamble
+ \from{texosquery.dtx}{texosquery-jre5.sh}%
+ }
+ \file{texosquery-jre8.sh}%
+ {%
+ \usepreamble\bashpreamble\nopostamble
+ \from{texosquery.dtx}{texosquery-jre8.sh}%
+ }
+ \file{texosquery.batch}%
+ {%
+ \nopreamble\nopostamble
+ \from{texosquery.dtx}{texosquery.bat}%
+ }
+ \file{texosquery-jre5.batch}%
+ {%
+ \nopreamble\nopostamble
+ \from{texosquery.dtx}{texosquery-jre5.bat}%
+ }
+ \file{texosquery-jre8.batch}%
+ {%
+ \nopreamble\nopostamble
+ \from{texosquery.dtx}{texosquery-jre8.bat}%
+ }
+}
+
+\endbatchfile
+
diff --git a/support/texosquery/texosquery.pdf b/support/texosquery/texosquery.pdf
new file mode 100644
index 0000000000..51aee9d372
--- /dev/null
+++ b/support/texosquery/texosquery.pdf
Binary files differ