arab numerals - roman numerals

Get help using and writing Nisus Writer Pro macros.
Post Reply
js
Posts: 259
Joined: 2007-04-12 14:59:36

arab numerals - roman numerals

Post by js »

Has there ever been a macro to output roman numerals from arab ones and vice versa? Or maybe this is not as simple as that?
Kino
Posts: 400
Joined: 2008-05-17 04:02:32

Re: arab numerals - roman numerals

Post by Kino »

Old macros written before NWP 1.3; perhaps they can be simplified. Both work on selected numerals.

Code: Select all

### Arabic Numeral to Roman Numeral ###

# Convert Arabic numeral(s) in selection(s) to Roman numeral(s).

# Only numerals from 1 to 3999 are supported.

Require Application Version '3.2'

$doc = Document.active

$thousands = ','
$comma = System Property 'use comma for decimal point'
if $comma == true
	$thousands = '.'
end
$findArabicNumerals = '[0-9]+(?:[0-9' & $thousands  # construct a find expression for Arabic numerals
$findArabicNumerals &= '])*'

$numfound = Find All $findArabicNumerals, 'Es'  # E: PowerFind Pro; s: in selections
if ! $numfound
	Exit 'No Arabic numeral found in selection(s), exit...'
end

$selections = $doc.textSelections
$romanIXCM = Array.new ('I', 'X', 'C', 'M')  # create an array
$romanVLD = Array.new ('V', 'L', 'D')  # create another array
$errorMessage = ''

foreach $sel in reversed $selections
	$validArabicNumeral = true
	$roman = ''
	$arabic = $sel.subtext
	$arabic.replaceAll $thousands, ''
	if $arabic > 3999
		$validArabicNumeral = false
		$errorMessage &= 'Cannot convert ' & $sel.subtext
		$errorMessage &= " which is greater than 3999.\n"
	end
	if $arabic < 1
		$validArabicNumeral = false
		$errorMessage &= 'Cannot convert ' & $sel.subtext
		$errorMessage &= " which is less than 1.\n"
	end
	if $validArabicNumeral == true
		$arabic = $arabic.split ''
		$i = 0
		$j = $arabic.count - 1
		while $i < $j  # reverse values in $arabic
			$arabic.swapValuesAtIndexes $i, $j
			$i += 1
			$j -= 1
		end
		$limit = $arabic.count
		$i = 0
		while $i < $limit
			if $arabic[$i] == 4
					# When $i is 0, $romanIXCM[0] is "I" and $romanVLD[0] is "V".
				$roman = $romanVLD[$i] & $roman
				$roman = $romanIXCM[$i] & $roman
			elsif $arabic[$i] == 9 
				$roman = $romanIXCM[$i+1] & $roman
				$roman = $romanIXCM[$i] & $roman
			elsif $arabic[$i] < 4
					# When $i is 2, $romanIXCM[2] is "C" and $romanIXCM[2+1] is "M".
					# When $i is 3, $romanIXCM[3] is "M" and $arabic[3] is "1".
				$c = $arabic[$i]
				while $c > 0
					$roman = $romanIXCM[$i] & $roman
					$c -= 1
				end
			else  # if $arabic[$i] is 6 or 7 or 8
					# When $i is 1, $romanVLD[1] is "L", $romanIXCM[1] is "X"
					# and $arabic[1] is "8". So ($arabic[1] - 5) = 3.
				$c = $arabic[$i] - 5
				while $c > 0
					$roman = $romanIXCM[$i] & $roman
					$c -= 1
				end
				$roman = $romanVLD[$i] & $roman
			end
			$i += 1
		end
	$roman = Cast to String $roman
	$sel.text.replaceInRange $sel.range, $roman
	end
end

if $errorMessage
	Exit $errorMessage
end

Code: Select all

### Roman Numeral to Arabic Numeral ###

# Convert Roman numeral(s) in selection(s) to Arabic numeral(s).

# Only numbers from 1 to 3999 are supported.

Require Application Version '3.1'
$doc = Document.active
$selections = $doc.textSelections
$errorMessage = ''
if ! $selections.firstValue.length
	Exit 'Nothing selected, exit...'
end

$errorMessage = ''
$roman2arabic = Hash.new
$roman2arabic{'I'} = '1'
$roman2arabic{'V'} = '5'
$roman2arabic{'X'} = '10'
$roman2arabic{'L'} = '50'
$roman2arabic{'C'} = '100'
$roman2arabic{'D'} = '500'
$roman2arabic{'M'} = '1000'

foreach $sel in reversed $selections
	$roman = $sel.subtext
	$roman.replaceAll '\x20|\xA0', '', 'E'
		# Transformm, for example, "M CM XL III" to "MCMXLIII"
		#  \x20  space
		#  \xA0  no-break space
		#  |     OR operator
	$matched = $roman.find '(?x)  # See if it is a valid roman numeral
			^(
				(?: M{1,3} )?
				(?: D?C{1,3} | C?[DM] )?
				(?: L?X{1,3} | X?[LC] )?
				(?: V?I{1,3} | I?[VX] )?
			)$', 'Ei'  # (?x) eXtended mode (ignore white space)
		#  ^ strings start
		#  (    )  group (captured)
		#  (?:  )  group (not captured)
		#  ?       zero or one time
		#  {1,3}   match at least 1 but not more than 3 times
		#  |       OR operator
	if ! $matched.length # if $matched is empty, then it is an invalid roman numeral
		$errorMessage &= "\"$roman\" is not a valid Roman numeral.\n"
	else
		$roman.replaceAll '(?=I[VX])|(?=X[LC])|(?=C[DM])', '-', 'Ei'
			# insert "-" before 
			#   "I" preceding "V" or "X",
			#   "X" preceding "L" or "C" and
			#   "C" preceding "D" or "M"
			# because they are negative
			# to transform "MCMXLIII" into "M-CM-XLIII"
		$roman.replaceAll '(?<!-)(?!-)(?=\S)', '+', 'E'
			# insert "+" before non-minus character
			# and non-minus character
			# to transform "M-CM-XLIII" into "+M-C+M-X+L+I+I+I"
		$range = Range.new 0, $roman.length
		$roman.transliterateInRange $range, $roman2arabic  # replace "I" with "1"
		$i = 0
		$plus = $minus = Array.new
		$plusSels = $roman.findAll '(?<=\+)[^+-]+', 'E'
		foreach $n in $plusSels
			$plus.appendValue $n.subtext
		end
		foreach $n in $plus
			$i += $n
		end
		$minusSels = $roman.findAll '(?<=-)[^+-]+', 'E'
		foreach $n in $minusSels
			$minus.appendValue $n.subtext
		end
		foreach $n in $minus
			$i -= $n
		end
		$sel.text.replaceInRange $sel.range, $i
	end
end

if $errorMessage  # if $errorMessage is not empty
	Exit $errorMessage  # show $errorMessage
end
js
Posts: 259
Joined: 2007-04-12 14:59:36

Re: arab numerals - roman numerals

Post by js »

I can see that this is not so simple indeed.
I find a problem on the roman Arabic side though. The macro does not execute and complains about the length property "requiring an object under $matched".
Kino
Posts: 400
Joined: 2008-05-17 04:02:32

Re: arab numerals - roman numerals

Post by Kino »

js wrote:The macro does not execute and complains about the length property "requiring an object under $matched".
That is no-break spaces inserted by php. Run

Code: Select all

Replace All '\xA0', '\x20', 'E'
on your macro file.
js
Posts: 259
Joined: 2007-04-12 14:59:36

Re: arab numerals - roman numerals

Post by js »

Thank you. that works fine now.
I have a suggestion to make: Probably it would be a good idea to provide for lowercase roman, as is often used in Prefaces. For Roman -> Arabic one can of course just start the Macro with the line "Convert:To UPPERCASE", while for Arabic -> Roman the (multiple) selections should be kept to change from upper to lower case if needed.
Kino
Posts: 400
Joined: 2008-05-17 04:02:32

Re: arab numerals - roman numerals

Post by Kino »

js wrote:for Arabic -> Roman
Duplicate the macro and just make lowercase the definitions of $romanIXCM and $romanVLD, i.e.

Code: Select all

$romanIXCM = Array.new ('i', 'x', 'c', 'm')
$romanVLD = Array.new ('v', 'l', 'd')
Groucho
Posts: 497
Joined: 2007-03-03 09:55:06
Location: Europe

Re: arab numerals - roman numerals

Post by Groucho »

You can specify which case with a prompt. Substitute this piece of code:

Code: Select all

$Case = prompt options "Do you want roman numerals to be uppercase or lowercase?", "", "OK", "UPPERCASE", "lowercase"
if $Case == "UPPERCASE"
	$romanIXCM = Array.new ('I', 'X', 'C', 'M')  # create an array
	$romanVLD = Array.new ('V', 'L', 'D')  # create another array
	elsif $Case == "lowercase"
	$romanIXCM = Array.new ('i', 'x', 'c', 'm')  # create an array
	$romanVLD = Array.new ('v', 'l', 'd')  # create another array
end
for this

Code: Select all

$romanIXCM = Array.new ('I', 'X', 'C', 'M')  # create an array
$romanVLD = Array.new ('V', 'L', 'D')  # create another array
Henry.
User avatar
phspaelti
Posts: 1313
Joined: 2007-02-07 00:58:12
Location: Japan

Re: arab numerals - roman numerals

Post by phspaelti »

I know Kino is a very clever person, and excellent macro writer. So Roman numerals are much too stupid a technology for him. I think his approach to this problem is way too obscure. Sometimes less is more.

So here is my approach. I have stolen Kino's excellent approach to handling the input, but I replaced the actual conversion with what I think is a much more intuitive approach. This code is also certainly easier to maintain.

Code: Select all

### Arabic Numeral to Roman Numeral ###

# Convert Arabic numeral(s) in selection(s) to Roman numeral(s).

# Only numerals from 1 to 3999 are supported.

Require Application Version '3.2'

$doc = Document.active

$thousands = ','
$comma = System Property 'use comma for decimal point'
if $comma == true
	$thousands = '.'
end
$findArabicNumerals = '[0-9]+(?:[0-9' & $thousands  # construct a find expression for Arabic numerals
$findArabicNumerals &= '])*'

$numfound = Find All $findArabicNumerals, 'Es'  # E: PowerFind Pro; s: in selections
if ! $numfound
	Exit 'No Arabic numeral found in selection(s), exit...'
end

$selections = $doc.textSelections
$errorMessage = ''

foreach $sel in reversed $selections
	$validArabicNumeral = true
	$roman = ''
	$arabic = $sel.subtext
	$arabic.replaceAll $thousands, ''
	if $arabic > 3999
		$validArabicNumeral = false
		$errorMessage &= 'Cannot convert ' & $sel.subtext
		$errorMessage &= " which is greater than 3999.\n"
	end
	if $arabic < 1
		$validArabicNumeral = false
		$errorMessage &= 'Cannot convert ' & $sel.subtext
		$errorMessage &= " which is less than 1.\n"
	end
	if $validArabicNumeral
		while $arabic > 999
			$arabic -= 1000
			$roman &= ‘M’
		end
		if $arabic > 499
			$arabic -= 500
			$roman &= ‘D’
		end
		while $arabic > 99
			$arabic -= 100
			$roman &= ‘C’
		end
		if $arabic > 49
			$arabic -= 50
			$roman &= ‘L’
		end
		while $arabic > 9
			$arabic -= 10
			$roman &= ‘X’
		end
		if $arabic > 4
			$arabic -= 5
			$roman &= ‘V’
		end
		while $arabic > 0
			$arabic -= 1
			$roman &= ‘I’
		end
		$roman.findAndReplace ‘DCCCC’, ‘CM’
		$roman.findAndReplace ‘CCCC’, ‘CD’
		$roman.findAndReplace ‘LXXXX’, ‘XC’
		$roman.findAndReplace ‘XXXX’, ‘XL’
		$roman.findAndReplace ‘VIIII’, ‘IX’
		$roman.findAndReplace ‘IIII’, ‘IV’
		$sel.text.replaceInRange $sel.range, $roman
	end
end

if $errorMessage
	Exit $errorMessage
end
philip
User avatar
martin
Official Nisus Person
Posts: 5227
Joined: 2002-07-11 17:14:10
Location: San Diego, CA
Contact:

Re: arab numerals - roman numerals

Post by martin »

phspaelti wrote:So Roman numerals are much too stupid a technology for him
haha, yes, Roman numerals are a pain to work with. I don't know anything about the history behind them, but they seem quite impractical as compared to Arabic numbers.

It'd be nice if the macro language gave a coder access to all the built-in number formatters, though I don't think such a feature would be used often.
User avatar
phspaelti
Posts: 1313
Joined: 2007-02-07 00:58:12
Location: Japan

Re: arab numerals - roman numerals

Post by phspaelti »

Well I think it is probably wrong to think that Roman numerals are numbers in any sense that we know. They really just an abbreviatory way to write out numbers as words.

One thing though is that Roman numerals reflect pretty directly the numbers as represented on an abacus. Presumably Romans would never have done math on paper the way we do, but would have done all math on an abacus.

Anyhow, my last attempt at this macro was a bit too primitive. Here is a slightly improved version that makes use of a hash:

Code: Select all

### Arabic Numeral to Roman Numeral ###

# Convert Arabic numeral(s) in selection(s) to Roman numeral(s).

# Only numerals from 1 to 3999 are supported.

Require Application Version '3.2'

$doc = Document.active

$thousands = ','
$comma = System Property 'use comma for decimal point'
if $comma == true
	$thousands = '.'
end
$findArabicNumerals = '[0-9]+(?:[0-9' & $thousands  # construct a find expression for Arabic numerals
$findArabicNumerals &= '])*'

$numfound = Find All $findArabicNumerals, 'Es'  # E: PowerFind Pro; s: in selections
if ! $numfound
	Exit 'No Arabic numeral found in selection(s), exit...'
end

$selections = $doc.textSelections
$errorMessage = ''

$romanNumerals = Hash.new 1,‘I’, 5,‘V’, 10,‘X’, 50,‘L’, 100,‘C’, 500,‘D’, 1000,‘M’
$romanDigitValues = $romanNumerals.keys
$romanDigitValues.sort

foreach $sel in reversed $selections
	$validArabicNumeral = true
	$roman = ''
	$arabic = $sel.subtext
	$arabic.replaceAll $thousands, ''
	if $arabic > 3999
		$validArabicNumeral = false
		$errorMessage &= 'Cannot convert ' & $sel.subtext
		$errorMessage &= " which is greater than 3999.\n"
	end
	if $arabic < 1
		$validArabicNumeral = false
		$errorMessage &= 'Cannot convert ' & $sel.subtext
		$errorMessage &= " which is less than 1.\n"
	end
	if $validArabicNumeral
		foreach $romanDigitValue in reversed $romanDigitValues
			while $arabic >= $romanDigitValue
				$arabic -= $romanDigitValue
				$roman &= $romanNumerals{$romanDigitValue}
			end
		end
		$roman.findAndReplace ‘DCCCC’, ‘CM’
		$roman.findAndReplace ‘CCCC’, ‘CD’
		$roman.findAndReplace ‘LXXXX’, ‘XC’
		$roman.findAndReplace ‘XXXX’, ‘XL’
		$roman.findAndReplace ‘VIIII’, ‘IX’
		$roman.findAndReplace ‘IIII’, ‘IV’
		$sel.text.replaceInRange $sel.range, $roman
	end
end

if $errorMessage
	Exit $errorMessage
end
philip
User avatar
phspaelti
Posts: 1313
Joined: 2007-02-07 00:58:12
Location: Japan

Re: arab numerals - roman numerals

Post by phspaelti »

And here also a version for the reverse conversion. Again I used the input/output of Kino's macro, and just 'simplified' the conversion. I also 'loosened' the types of numerals the macro accepts.

Code: Select all

### Roman Numeral to Arabic Numeral ###

# Convert Roman numeral(s) in selection(s) to Arabic numeral(s).

# Only numbers from 1 to 3999 are supported.

Require Application Version '3.1'
$doc = Document.active
$selections = $doc.textSelections
$errorMessage = ''
if ! $selections.firstValue.length
	Exit 'Nothing selected, exit...'
end

$errorMessage = ''
$romanNumerals = Hash.new ‘I’,1, ‘V’,5, ‘X’,10, ‘L’,50, ‘C’,100, ‘D’,500, ‘M’,1000


foreach $sel in reversed $selections
	$roman = $sel.subtext
	$roman.replaceAll '\x20|\xA0', '', 'E'
		# Transform, for example, "M CM XL III" to "MCMXLIII"
		#  \x20  space
		#  \xA0  no-break space
		#  |	  OR operator
	$isRoman = $roman.find '(?x)^(
			(?: M*)?
			(?: D?C{1,4} | C?[DM])?
			(?: L?X{1,4} | X?[LC])?
			(?: V?I{1,4} | I?[VX])?
				)$', 'Ei' #  ^ strings start
		#  (	 )  group (captured)
		#  (?:  )  group (not captured)
		#  ?	 	zero or one time
		#  {1,3}	match at least 1 but not more than 3 times
		#  |	 	OR operator
	if ! $isRoman
		$errorMessage &= "\"$roman\" is not a valid Roman numeral.\n"
	else
		$romanLetters = $roman.split ‘’
		$arabic = 0
		$lastLetterValue = 0
		foreach $letter in $romanLetters
			$letterValue = $romanNumerals{$letter}
			if $lastLetterValue < $letterValue
				$arabic -= $lastLetterValue
			else
				$arabic += $lastLetterValue
			end
			$lastLetterValue = $letterValue
		end
		$arabic += $lastLetterValue
		$sel.text.replaceInRange $sel.range, $arabic
	end
end

if $errorMessage  # if $errorMessage is not empty
	Exit $errorMessage  # show $errorMessage
end
philip
Post Reply