Reply to topic  [ 11 posts ] 
arab numerals - roman numerals 
Author Message

Joined: 2007-04-12 14:59:36
Posts: 229
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?


2010-06-12 03:04:40
Profile

Joined: 2008-05-17 04:02:32
Posts: 400
Old macros written before NWP 1.3; perhaps they can be simplified. Both work on selected numerals.
Code:
### 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:
### 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


2010-06-12 05:03:37
Profile

Joined: 2007-04-12 14:59:36
Posts: 229
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".


2010-06-13 14:16:56
Profile

Joined: 2008-05-17 04:02:32
Posts: 400
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:
Replace All '\xA0', '\x20', 'E'
on your macro file.


2010-06-13 18:35:17
Profile

Joined: 2007-04-12 14:59:36
Posts: 229
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.


2010-06-14 06:38:30
Profile

Joined: 2008-05-17 04:02:32
Posts: 400
js wrote:
for Arabic -> Roman
Duplicate the macro and just make lowercase the definitions of $romanIXCM and $romanVLD, i.e.
Code:
$romanIXCM = Array.new ('i', 'x', 'c', 'm')
$romanVLD = Array.new ('v', 'l', 'd')


2010-06-14 08:03:54
Profile

Joined: 2007-03-03 09:55:06
Posts: 494
Location: Europe
You can specify which case with a prompt. Substitute this piece of code:
Code:
$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:
$romanIXCM = Array.new ('I', 'X', 'C', 'M')  # create an array
$romanVLD = Array.new ('V', 'L', 'D')  # create another array


Henry.


2010-06-15 08:05:52
Profile
User avatar

Joined: 2007-02-07 00:58:12
Posts: 876
Location: Japan
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:
### 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


2010-06-17 17:24:31
Profile
Official Nisus Person
User avatar

Joined: 2002-07-11 17:14:10
Posts: 4251
Location: San Diego, CA
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.


2010-06-18 10:37:11
Profile WWW
User avatar

Joined: 2007-02-07 00:58:12
Posts: 876
Location: Japan
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:
### 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


2010-06-19 08:49:49
Profile
User avatar

Joined: 2007-02-07 00:58:12
Posts: 876
Location: Japan
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:
### 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


2010-06-19 09:21:20
Profile
Display posts from previous:  Sort by  
Reply to topic   [ 11 posts ] 

Who is online

Users browsing this forum: No registered users and 6 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group
Designed by ST Software