Skip to content

Instantly share code, notes, and snippets.

@joewiz
Last active October 17, 2022 16:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joewiz/228e9cc174694e146cc8 to your computer and use it in GitHub Desktop.
Save joewiz/228e9cc174694e146cc8 to your computer and use it in GitHub Desktop.
Convert Roman numerals to integers, with XQuery, https://joewiz.org/2021/05/30/converting-roman-numerals-with-xquery-xslt/
xquery version "3.0";
module namespace r = "http://joewiz.org/ns/xquery/roman-numerals";
(: Converts standard Roman numerals to integers.
: Handles additive and subtractive but not double subtractive.
: Case insensitive.
: Doesn't attempt to validate a numeral other than a naïve character check.
: See discussion of standard modern Roman numerals at:
: http://en.wikipedia.org/wiki/Roman_numerals.
: Adapted from an XQuery 1.0 module at:
: https://github.com/subugoe/ropen-backend/blob/master/src/main/xquery/queries/modules/roman-numerals.xqm
: Credit to Mattio Valentino:
: https://twitter.com/joewiz/status/1398359600257290242
:)
declare function r:decode-roman-numeral($input as xs:string) as xs:integer {
let $characters := string-to-codepoints(upper-case($input)) ! codepoints-to-string(.)
let $character-to-integer :=
function($character as xs:string) {
switch ($character)
case "I" return 1
case "V" return 5
case "X" return 10
case "L" return 50
case "C" return 100
case "D" return 500
case "M" return 1000
default return error(xs:QName('roman-numeral-error'), concat('Invalid input: ', $input, '. Valid Roman numeral characters are I, V, X, L, C, D, and M. This function is case insensitive.'))
}
let $numbers := $characters ! $character-to-integer(.)
let $values :=
for $number at $n in $numbers
return
if ($number < $numbers[position() = $n + 1]) then
-$number (: Handles subtractive notation of Roman numerals. :)
else
$number
return
sum($values)
};
xquery version "3.0";
(: Converts Roman numerals to integers.
: Pre-computes Roman numerals for 1-3999.
: Inspired by David Sewell's tweet:
: https://twitter.com/DavidSewellVA/status/1399036814736953351
:)
module namespace r = "http://joewiz.org/ns/xquery/roman-numerals";
declare variable $r:roman-numerals := (1 to 3999) ! format-integer(., "I");
declare function r:decode-roman-numeral($roman-numeral as xs:string) {
index-of($r:roman-numerals, upper-case($roman-numeral))
};
xquery version "3.1";
module namespace r = "http://joewiz.org/ns/xquery/roman-numerals";
(: Converts Roman numerals to integers.
: Adapted from Clojure version at:
: https://github.com/OpenRefine/OpenRefine/wiki/Recipes#convert-roman-numerals-to-arabic
: Written up at:
: https://joewiz.org/2021/05/30/converting-roman-numerals-with-xquery-xslt/
: See also:
: https://rosettacode.org/wiki/Roman_numerals/Decode
:)
declare function r:decode-roman-numeral($roman-numeral as xs:string) as xs:integer {
$roman-numeral
=> upper-case()
=> for-each(
function($roman-numeral-uppercase) {
analyze-string($roman-numeral-uppercase, ".")/fn:match/string()
}
)
=> for-each(
map { "M": 1000, "D": 500, "C": 100, "L": 50, "X": 10, "V": 5, "I": 1 }
)
=> fold-right( [0, 0],
function($number as xs:integer, $accumulator as array(*)) {
let $running-total := $accumulator?1
let $previous-number := $accumulator?2
return
if ($number lt $previous-number) then
[ $running-total - $number, $number ]
else
[ $running-total + $number, $number ]
}
)
=> array:head()
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment