#!/usr/bin/ruby

=begin rdoc

= NAME

hexja - 日本語対応 16 進数ダンプツール
 
= SYNOPSIS

hexja [options] [filename...]

hexja -r [filename...]
 
= DESCRIPTION
 
指定されたファイルまたは標準入力の内容を16進数と文字列で表示します。
その際、日本語らしき部分は日本語で表示します。

= OPTIONS

オプションは環境変数 HEXJA_OPTIONS に設定しておくことができます。

== 入力文字コード

入力文字コードのデフォルトは、
環境変数 LANG が存在すれば、それに合わせた文字コードになります。
LANG が存在しないか、適切な日本語文字コードを LANG から判断できない場合は、
システムによって CP932 (Windows) か UTF-8 (その他のシステム) になります。

[-i <i>INPUT_CODING_SYSTEM</i>]
	入力文字コードを指定します。
	UTF-8, CP932, SHIFT_JIS, UTF-16BE, UTF-16LE, EUC-JP
	の何れかを指定可能です。
[-U, --iunknown]
	日本語に関する処理をしません。
[-S]
	入力をシフト JIS 系であると仮定します。
	Windows 上では --icp932 と同等です。
	その他のシステムでは --isjis と同等です。
[--icp932]
	入力文字コードを CP932 か ISO-2022-JP-1 だと仮定します。
[--isjis]
	入力文字コードを SHIFT_JIS か ISO-2022-JP-1 だと仮定します。
[-E, --ieuc]
	入力文字コードを EUC-JP か ISO-2022-JP-1 だと仮定します。
[-8, --iutf8]
	入力文字コードを UTF-8 か ISO-2022-JP-1 だと仮定します。
[--iutf16, --iutf16le]
	入力文字コードを UTF-16LE だと仮定します。
[--iutf16be]
	入力文字コードを UTF-16BE だと仮定します。
[--2byte-aligned]
	UTF-16 が 2 バイト境界に整列していると仮定します。
	--iutf16, --iutf16le, --iutf16be が指定された時のみ意味を持ちます。
[--siso]
	ISO 2022 の shift-in, shift-out を利用した半角カタカナが
	入力に含まれると仮定します。
	--iutf16, --iutf16le, --iutf16be が
	指定されなかった時のみ意味を持ちます。

== 出力文字コード

[-o <i>OUTPUT_CODING_SYSTEM</i>]
	出力文字コードを指定します。
	UTF-8, CP932, SHIFT_JIS, EUC-JP, ISO-2022-JP, ISO-2022-JP-1
	の何れかを指定可能です。
[-s]
	Windows 上では --ocp932 と同等です。
	その他のシステムでは --osjis と同等です。
[--ocp932]
	出力文字コードを CP932 にします。
[--osjis]
	出力文字コードを SHIFT_JIS にします。
[-e, --oeuc]
	出力文字コードを EUC-JP にします。
[--outf8]
	出力文字コードを UTF-8 にします。

== 出力形式の指定

[-c, --color]
	表示できない文字
	(コントロール文字や、日本語文字コードで表現できない文字など)
	に色をつけて表示します。
	端末へ表示する場合のデフォルトです。
[-d, --dot]
	表示できない文字
	(コントロール文字や、日本語文字コードで表現できない文字など)
	を「.」で表示します。
	hexja の出力をパイプに通したりした場合のデフォルトです。
[--html]
	スタイルシート (CSS) を利用した HTML で出力します。
	-d オプションが指定されている時は、CSS は利用されず、色もつきません。
[--html-no-css]
	スタイルシートを利用せず、<font> タグを利用して HTML を出力します。
	-d オプションが指定されている時は、
	<font> タグは利用されず、色もつきません。

== 出力時の色の指定

出力時の色は、--html, --html-no-css が
指定されている場合は ##RRGGBB の形式で指定することができます。
又、showrgb コマンド (/usr/X11R6/bin/showrgb など) が存在する場合には
色の名前で指定することもできます。

それ以外の場合は、ISO 6429 の色コードを指定します。
たとえば、黒:30, 赤:31, 緑:32, 黄:33, 青:34, 紫:35, 水:36, 白:37 などです。
(さらに、--color-letter '1;30;43' などと指定することで、
太字、黒色、背景黄色で表示することもできます)

[--color-letter <i>COLOR</i>]
	通常の文字の色を設定します。
	デフォルトは端末の文字色、
	あるいは、ブラウザのデフォルトの文字色になります。
[--color-non-letter <i>COLOR</i>]
	表示できない文字の色を設定します。
	デフォルトでは青 (34, ##aaaaff) になります。
[--color-breaked-letter <i>COLOR</i>]
	文字の一部である部分の色を設定します。
	デフォルトでは赤 (31, ##ff0000) になります。
[--color-iso-seq <i>COLOR</i>]
	ISO-2022-JP-1 のエスケープシーケンスの色を設定します。
	デフォルトでは水色 (36, ##00ffff) になります。

== その他

[-r, --restore]
	hexja の出力形式をバイナリへ変換します。
	 % hexja < foo | hexja -r > bar
	は
	 % cat < foo > bar
	と同じ結果になります。
[-h, --help]
	ヘルプを表示します。
[--version]
	バージョンを表示します。

== 旧バージョンとの違い

旧バージョン (hex 2.04) では以下のオプションが使用可能でしたが、
廃止されました。同等機能の別のオプションを使用してください。

-oe, -os, -oj,
-ie, -is, -iu,
-cs1, --colorstring1, -cs2, --colorstring2
-dsiso, --disablesiso, +siso,
--enablesiso,

旧バージョンでは ISO 2022 の shift-in, shift-out を利用した半角カタカナは
デフォルトで認識されましたが、
hexja では --siso オプションを指定しないと認識しません。

以下のオプションは廃止され、代替機能は存在しません。

-b, --bold, -t, --text, -u, --underline,

旧バージョンでは環境変数は HEX_OPTIONS を参照しますが、
hexja では HEXJA_OPTIONS を参照します。

= TIPS

hexja の出力を less や lv などへ渡すとそのままでは色が付きませんので、
以下のようにすると良いでしょう。

 % hexja -c foo | less -r
 % hexja -c foo | lv -c
 
= NOTE

- SHIFT_JIS の 0x7e は Unicode では OVERLINE (U+203e) へマップされているが、
  画面には代わりに ~ (U+007e) を表示しています。
  これは Windows や Mac OSX では OVERLINE を端末上で表示すると、
  全角文字の幅があるため都合が悪いからです。
- 合成用の濁点 (U+3099) と半濁点 (U+309a) は
  通常の濁点 (U+309b) と半濁点 (U+309c) を使用して表示します。
- 外字は表示されません。
- 全角チルダ (U+ff5e) は WAVE DASH (U+301c) を使用して
  表示されることがあります。
- JISX0213 には対応していません。

= SEE ALSO

od(1), hexdump(1), jhd(1), less(1), lv(1)

= AUTHOR

多賀奈由太 (TAGA Nayuta)

= HOMEPAGE

http://sourceforge.jp/projects/hexja/
 
= COPYRIGHT

いわゆる BSD ライセンスです。

Copyright (c) 1996-1998, 2006 TAGA Nayuta All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

=end

require 'iconv'
require 'optparse'
#require 'pp'

###############################################################################
# グローバル変数

# Windows 環境かどうか
$isMSWIN = (RUBY_PLATFORM =~ /bccwin|cygwin|mingw|mswin/)

# SHIFT_JIS を利用する代わりに CP932 を利用するかどうか
$useCP932 = $isMSWIN

# ^N(SO)/^O(SI) を利用するかどうか
$useSISO = false

# 色を利用するかどうか
$useColor = nil

# デフォルト文字コードを環境変数 LANG から得る。
if ENV['LANG'] =~ /utf-8|utf8/i
  $defaultCS = 'UTF-8'
elsif ENV['LANG'] =~ /eucjp|euc-jp|ujis/i
  $defaultCS = 'EUC-JP'
elsif ENV['LANG'] =~ /sjis/i
  $defaultCS = 'SHIFT_JIS'
elsif $isMSWIN
  $defaultCS = 'CP932'
else
  $defaultCS = 'UTF-8'
end

# 入出力文字コード
$inputCS = $defaultCS
$outputCS = $defaultCS
$isOutputCSUTF8 = nil           # オプション解析の後で設定される。
$alignedUTF16 = false # UTF-16 入力の時に、2バイト境界に整列しているかどうか。

# 色
$TTYC_NORMAL = "\x1b[m"
$TTYC_LETTER = "\x1b[m"
$TTYC_NON_LETTER = "\x1b[34m"
$TTYC_BREAKED_LETTER = "\x1b[31m"
$TTYC_ISO_SEQ = "\x1b[36m"

$HTML_TTYC_LETTER = nil
$HTML_TTYC_NON_LETTER = "#aaaaff"
$HTML_TTYC_BREAKED_LETTER = "#ff0000"
$HTML_TTYC_ISO_SEQ = "#00ffff"

# 戻すモード
$restoreMode = false

# HTML 出力モード (false :css :font)
$outputAsHTML = false

# ISO-2022-JP-1 を使用可能かどうか調査する
$iso2022jp1 = 'ISO-2022-JP-1'
begin
  Iconv.conv('ISO-2022-JP-1', 'UTF-8', 'test');
rescue
  $iso2022jp1 = 'ISO-2022-JP'
end

# Debug Message
$debugMessage = false

###############################################################################
# 何か関数とか

# str を文字コード cs で解釈した時に得られる UNICODE 文字列の、
# 最初の文字の UNICODE を返す。
# Iconv 関連の例外が発生する可能性がある。
# cs に UTF-16 系を指定してはいけない。
def to_unicode(cs, str)
  return str[0] if (str.size == 1 && str[0] < 0x20)
  utf16be = Iconv.conv('UTF-16BE', cs, str)
  return (utf16be[0] << 8) | utf16be[1]
end

# UTF-8 のテキストを $defaultCS 文字コードへ変換する。
# Iconv 関連の例外が発生する可能性がある。
def to_dcs(text)
  return Iconv.conv($defaultCS, 'UTF-8', text)
end

# text が色かどうかのチェックをし、色を返す。
# text が ##RRGGBB か showrgb(1) で得られる色名ならば ##RRGGBB 形式で返す。
# text が ISO 6429 の色コードの場合はそのまま返す。
# それ以外は、エラーを表示して終了する。
def parseColor(text)
  if text =~ /^#[0-9a-f]+$/i
    return text
  elsif text =~ /^[0-9]+(;[0-9]+)$/
    return text
  else
    if $colors == nil
      $colors = { }
      IO.popen('showrgb', 'r').each_line{|line|
        line.chomp!
        if line =~ /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S.*)$/
          r = $1
          g = $2
          b = $3
          color = $4
          $colors[color.downcase] = sprintf("#%02x%02x%02x", r, g, b)
        end
      }
    end
    if $colors[text.downcase] == nil
      print "unknown color: #{text}\n"
      exit 1
    end
    return $colors[text.downcase]
  end
end

###############################################################################
# オプション解析

argv = ARGV
if ENV["HEXJA_OPTIONS"]
  argv = ENV["HEXJA_OPTIONS"].split(/\s+/) + ARGV
end

OptionParser.new{|opt|
  # バージョンとバナー
  opt.version = "3.2"
  banner = <<"__EOM__"
usage: hexja [options] [filename...]
       hexja -r [filename...]
指定されたファイルまたは標準入力の内容を16進数と文字列で表示します。
その際、日本語らしき部分は日本語で表示します。
このシステムでのデフォルト文字コードは#{$defaultCS}です。
__EOM__
  opt.banner = to_dcs(banner)

  opt.separator ""
  opt.separator to_dcs("入力文字コード:")

  # 入力文字コード obsolete: -ie, -is
  opt.on('-i INPUT_CODING_SYSTEM', to_dcs("入力文字コードの指定")){|v|
    $inputCS = v
  }
  opt.on('-U', '--iunknown', to_dcs('日本語に関する処理はしない')){|v|
    $inputCS = nil if v
  }
  if ($useCP932)
    opt.on('-S', '--icp932', to_dcs("入力をCP932か#{$iso2022jp1}だと仮定")){|v|
      $inputCS = 'CP932' if v
    }
    opt.on('--isjis', to_dcs("入力をSHIFT_JISか#{$iso2022jp1}だと仮定")){|v|
      $inputCS = 'SHIFT_JIS' if v
    }
  else
    opt.on('-S', '--isjis',
           to_dcs("入力をSHIFT_JISか#{$iso2022jp1}だと仮定")){|v|
      $inputCS = 'SHIFT_JIS' if v
    }
    opt.on('--icp932', to_dcs("入力をCP932か#{$iso2022jp1}だと仮定")){|v|
      $inputCS = 'CP932' if v
    }
  end
  opt.on('-E', '--ieuc', to_dcs("入力をEUC-JPか#{$iso2022jp1}だと仮定")){|v|
    $inputCS = 'EUC-JP' if v
  }
  opt.on('-8', '--iutf8', to_dcs('入力をUTF-8と仮定')){|v|
    $inputCS = 'UTF-8' if v
  }
  opt.on('--iutf16', '--iutf16le', to_dcs('入力をUTF-16LEだと仮定')){|v|
    $inputCS = 'UTF-16LE' if v
  }
  opt.on('--iutf16be', to_dcs('入力をUTF-16BEだと仮定')){|v|
    $inputCS = 'UTF-16BE' if v
  }
  opt.on('--2byte-aligned', to_dcs('UTF-16は2バイト境界に整列していると仮定')){|v|
    $alignedUTF16 = v
  }
  # obsolete: -dsiso, --disablesiso, +siso -siso, --enablesiso
  opt.on('--siso', to_dcs('^N/^Oによる半角カタカナを有効にする')){|v|
    $useSISO = v
  }

  opt.separator ""
  opt.separator to_dcs("出力文字コード:")

  # 出力文字コード obsolete: -oe, -os
  opt.on('-o OUTPUT_CODING_SYSTEM', to_dcs('出力文字コードの指定')){|v|
    $outputCS = v
  }
  if ($useCP932)
    opt.on('-s', '--ocp932', to_dcs('CP932(Windows用シフトJIS)で出力')){|v|
      $outputCS = 'CP932' if v 
    }
    opt.on('--osjis', to_dcs('SHIFT_JISで出力')){|v|
      $outputCS = 'SHIFT_JIS' if v
    }
  else
    opt.on('-s', '--osjis', to_dcs('SHIFT_JISで出力')){|v|
      $outputCS = 'SHIFT_JIS' if v
    }
    opt.on('--ocp932', to_dcs('CP932(Windows用シフトJIS)で出力')){|v|
      $outputCS = 'CP932' if v
    }
  end
  opt.on('-e', '--oeuc', to_dcs('EUC-JPで出力')){|v|
    $outputCS = 'EUC-JP' if v
  }
  opt.on('--outf8', to_dcs('UTF-8で出力')){|v|
    $outputCS = 'UTF-8' if v
  }

  opt.separator ""
  opt.separator to_dcs("出力形式:")

  # 文字の色など
  opt.on('-c', '--color', to_dcs('表示できない文字に色をつけて表示')){|v|
    $useColor = v
  }
  #opt.on('-b', '--bold', '(未サポート)'){|v| }
  opt.on('-d', '--dot', to_dcs('表示できない文字を「.」で表示')){|v|
    $useColor = !v
  }
  #opt.on('-t', '--text', '(未サポート)'){|v| }
  #opt.on('-u', '--underline', '(未サポート)'){|v| }

  # html output
  opt.on('--html', to_dcs('HTML形式で出力(CSS)')){|v|
    $outputAsHTML = (v ? :css : nil)
  }
  opt.on('--html-no-css', to_dcs('HTML形式で出力(<font>)')){|v|
    $outputAsHTML = :font if v
  }
  
  # 色 obsolete: -cs1, -cs2
  opt.on('--color-letter COLOR', to_dcs('通常の文字の色')){|v|
    v = parseColor(v)
    if v =~ /^#[0-9a-f]+$/i
      $HTML_TTYC_LETTER = v
    else
      $TTYC_LETTER = "\x1b[" + v + "m"
    end
  }
  opt.on('--color-non-letter COLOR', to_dcs('表示できない文字の色')){|v|
    v = parseColor(v)
    if v =~ /^#[0-9a-f]+$/i
      $HTML_TTYC_NON_LETTER = v
    else
      $TTYC_NON_LETTER = "\x1b[" + v + "m"
    end
  }
  opt.on('--color-breaked-letter COLOR',
         to_dcs('文字の一部である部分の色')){|v|
    v = parseColor(v)
    if v =~ /^#[0-9a-f]+$/i
      $HTML_TTYC_BREAKED_LETTER = v
    else
      $TTYC_BREAKED_LETTER = "\x1b[" + v + "m"
    end
  }
  opt.on('--color-iso-seq COLOR',
         to_dcs("#{$iso2022jp1}のエスケープシーケンスの色")){|v|
    v = parseColor(v)
    if v =~ /^#[0-9a-f]+$/i
      $HTML_TTYC_ISO_SEQ = v
    else
      $TTYC_ISO_SEQ = "\x1b[" + v + "m"
    end
  }

  opt.separator ""
  opt.separator to_dcs("その他:")

  # restore
  opt.on('-r', '--restore', to_dcs('hexjaの出力形式をバイナリへ変換')){|v|
    $restoreMode = v
  }
  opt.on_tail("-h", "--help", "Show this message"){
    puts opt
    exit
  }
  opt.on_tail("--version", "Show version"){
    puts opt.version
    exit
  }
  opt.on_tail("--debug", "Show debug message"){|v|
    $debugMessage = v
  }
  
  opt.parse!(argv)
}

if $outputAsHTML == :css
  if $HTML_TTYC_LETTER
    $TTYC_LETTER = '<span class="letter">'
  else
    $TTYC_LETTER = nil
  end
  $TTYC_NON_LETTER = '<span class="non-letter">'
  $TTYC_BREAKED_LETTER = '<span class="breaked-letter">'
  $TTYC_ISO_SEQ = '<span class="iso-seq">'
  $useColor = true if $useColor == nil
elsif $outputAsHTML == :font
  if $HTML_TTYC_LETTER
    $TTYC_LETTER = '<font color="' + $HTML_TTYC_LETTER + '">'
  else
    $TTYC_LETTER = nil
  end
  $TTYC_NON_LETTER = '<font color="' + $HTML_TTYC_NON_LETTER + '">'
  $TTYC_BREAKED_LETTER = '<font color="' + $HTML_TTYC_BREAKED_LETTER + '">'
  $TTYC_ISO_SEQ = '<font color="' + $HTML_TTYC_ISO_SEQ + '">'
  $useColor = true if $useColor == nil
end

$isOutputCSUTF8 = ($outputCS =~ /UTF-?8/i);

$useColor = $stdout.tty? if $useColor == nil

if $debugMessage
  print "$isMSWIN = "; p $isMSWIN
  print "$useCP932 = "; p $useCP932
  print "$useSISO = "; p $useSISO
  print "$useColor = "; p $useColor
  print "$defaultCS = "; p $defaultCS
  print "$inputCS = "; p $inputCS
  print "$outputCS = "; p $outputCS
  print "$isOutputCSUTF8 = "; p $isOutputCSUTF8
  print "$alignedUTF16 = "; p $alignedUTF16
  print "$restoreMode = "; p $restoreMode
  print "$outputAsHTML = "; p $outputAsHTML
  print "$iso2022jp1 = "; p $iso2022jp1
end

###############################################################################
# 出力する文字列を保存するバッファ

class OutputBuffer
  def initialize
    @str = ""
    @prevColor = ""
  end

  # 色を表す文字列を追加する。
  # 既に同じ色を設定済みの場合は、なにもしない。
  # $outputAsHTML が :css の場合は、color は <span class="～"> という文字列。
  # $outputAsHTML が :font の場合は、color は <font color="～"> という文字列
  # である必要がある。
  def setColor!(color)
    if $useColor && @prevColor != color
      if @prevColor != "" && @prevColor != :default
        if $outputAsHTML == :css
          @str += '</span>'
        elsif $outputAsHTML == :font
          @str += '</font>'
        end
      end
      if color
        @str += color
        @prevColor = color
      else
        @prevColor = :default
      end
    end
  end

  # 文字列を基本的にそのまま追加する。
  # $outputAsHTML が真の場合は、& < > の文字は &amp; &lt; &gt; へ変換される。
  def add7BitStr!(str)
    if $outputAsHTML
      str = str.gsub(/&/, "&amp;")
      str = str.gsub(/</, "&lt;")
      str = str.gsub(/>/, "&gt;")
      @str += str
    else
      @str += str
    end
  end

  # UNICODE 文字1文字を追加する。
  # ただし、出力時の文字コードで表現できない文字の場合は '.' を width 個追加する。
  # また、U+ff5e FULLWIDTH TILDE が出力時の文字コードで表現できない場合は、
  # U+301C WAVE DASH を使用できるかどうかを試す。
  def addUnicode!(unicode, width)
    return if unicode == 0 || width == 0
    if unicode < 0x80
      add7BitStr!([ unicode ].pack('C'))
    else
      begin
        @str += Iconv.conv($outputCS, 'UTF-16BE', [ unicode ].pack('n'))
      rescue
        # U+ff5e FULLWIDTH TILDE → U+301C WAVE DASH
        if unicode == 0xff5e
          unicode = 0x301c 
          retry
        end
        setColor!($TTYC_NON_LETTER)
        (0...width).each{|i| @str += '.' }
      end
    end
  end

  # バッファの中身の文字列を得る。
  # $outputAsHTML が真の時は、閉じタグを追加することがある。
  def str
    if @prevColor == :default
      @prevColor = ''
    elsif @prevColor != ""
      if $outputAsHTML == :css
        @str += '</span>'
      elsif $outputAsHTML == :font
        @str += '</font>'
      end
      @prevColor = ''
    end
    return @str
  end
end

###############################################################################
# 1文字を保存するクラス

class Letter
  def initialize(bytes, unicode, strWidth, type)
    @bytes = bytes
    @unicode = unicode
    @strWidth = strWidth
    @type = type
  end
  
  # この文字のバイト列での表現 (Array インスタンス)
  attr_reader :bytes

  # この文字の UNICODE。
  # 画面上での見栄えを考慮して、本来の文字とは異なる文字が使用されることも多い。
  attr_reader :unicode

  # 端末へ表示したときに占める幅。
  # アルファベットなら 1、漢字やひらがななどなら 2 といった感じになる。
  attr_reader :strWidth
  
  # この文字のタイプ
  # [:letter] 正しく文字として解釈できる通常の文字。
  # [:nonLetter] 文字としては解釈できない文字。コントロールコードを含む。
  # [:breakedLetter] 文字の一部である文字。例えば漢字は端末上では幅 2 で表示されるが、
  #                  データとしては 3 バイトであることがある。その場合、3 バイト目は
  #                  :breakedLetter 扱いで表示される。Show#output でのみ使用される。
  # [:isoSeq] ISO-2022 の文字集合切り替えのエスケープシーケンスを表す。
  attr_reader :type
end

###############################################################################
# 画面へ整形して出力するためのクラス

class Show
  def initialize
    @seq = Array.new            # array of Letter
    @seqBytesSize = 0
    @address = 0
  end

  # :nonLetter なバイトを出力する。
  # bytes はサイズ 1 の Array, unicode は端末へ表示した時には幅が 1 になる文字。
  def nonLetter(bytes, unicode)
    @seq.push(Letter.new(bytes, unicode, 1, :nonLetter))
    @seqBytesSize += bytes.size
    flush16
  end

  # :letter な文字を出力する。
  # btyes はその文字を表現するバイト Array。
  # unicode はその文字を表す UNICODE。
  # width はその文字を端末へ表示した時に占める幅。
  def letter(bytes, unicode, width)
    @seq.push(Letter.new(bytes, unicode, width, :letter))
    @seqBytesSize += bytes.size
    flush16
  end

  # ISO-2022 の文字集合切り替えシーケンスを表すバイト列を出力する。
  def isoSeq(bytes)
    bytes.each{|byte|
      if byte <= 0x1f
        nonLetter([ byte ], byte + ?@)
      else
        @seq.push(Letter.new([ byte ], byte, 1, :isoSeq))
        @seqBytesSize += 1
      end
    }
    flush16
  end
  
  # unicode 文字を出力する。
  # 文字として表現可能ならば、:letter として。
  # 表現不能ならば、:nonLetter として出力する。
  # 表現可能かどうかは、主に出力文字コードに依存する。
  def unicodeLetter(bytes, unicode)
    if unicode <= 0x1f
      nonLetter(bytes, unicode + ?@)
    elsif unicode == 0x7f
      nonLetter(bytes, ??)
    elsif unicode <= 0x7f
      letter(bytes, unicode, 1)
    elsif unicode == 0x203e
      # SHIFT_JIS の 0x7e は U+203e になってしまうが、
      # U+203e はワイド幅なので都合が悪いので変更する
      letter(bytes, 0x7e, 1)
    elsif unicode == 0x3099
      # U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK (濁点)
      letter(bytes, 0x309b, 2) # U+309B KATAKANA-HIRAGANA VOICED SOUND MARK
    elsif unicode == 0x309a
      # U+309a COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK (半濁点)
      letter(bytes, 0x309c, 2)
      # U+309C KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
    elsif ((0xd800 <= unicode && unicode <= 0xdb7f) || # High Surrogates
           (0xdb80 <= unicode && unicode <= 0xdbff) || # High Private Use Surrogates
           (0xdc00 <= unicode && unicode <= 0xdfff) || # Low Surrogates
           (0xe000 <= unicode && unicode <= 0xf8ff)) # Private Use Area
      bytes.each{|i| nonLetter([ i ], ?.) }
    else
      # この文字が、$outputCS 文字コードで出力可能かどうかを調査
      begin
        Iconv.conv($outputCS, 'UTF-16BE', [ unicode ].pack('n'))
      rescue
        # U+ff5e FULLWIDTH TILDE → U+301C WAVE DASH
        if unicode == 0xff5e
          unicode = 0x301c 
          retry
        end
        # 失敗したから…
        bytes.each{|byte| nonLetter([ byte ], ?.)}
        return
      end

      # 文字幅を調査する
      
      utf16bePacked = [ unicode ].pack('n')
      begin
        packed = Iconv.conv('CP932', 'UTF-16BE', utf16bePacked);
        # SJISでは、画面に表示される文字の幅は、バイト数に一致する
        # FIXME: せっかくUNICODEなので、
        # SJISチェックをしないバージョンも作りたい
        letter(bytes, unicode, packed.size)
        return
      rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
      end
      
      begin
        packed = Iconv.conv('SHIFT_JIS', 'UTF-16BE', utf16bePacked);
        # SJISでは、画面に表示される文字の幅は、バイト数に一致する
        # FIXME: せっかくUNICODEなので、
        # SJISチェックをしないバージョンも作りたい
        letter(bytes, unicode, packed.size)
        return
      rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
      end
      
      begin
        packed = Iconv.conv($iso2022jp1, 'UTF-16BE', utf16bePacked);
        letter(bytes, unicode, 1 < packed.size ? 2 : 1)
        return
      rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
      end
      
      begin
        packed = Iconv.conv('EUC-JP', 'UTF-16BE', utf16bePacked);
        if packed.size == 1 || packed[0] == 0x8e
          size = 1
        else
          size = 2
        end
        letter(bytes, unicode, size)
        return
      rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
      end
      
      # Iconv::IllegalSequence: 変換できなかった場合
      # Iconv::InvalidCharacter: サローゲートペアの片割れなど
      bytes.each{|i| nonLetter([ i ], ?.) }
    end
  end

  # このクラスは、内部で 16 バイト分出力可能になった時に実際に出力するが、
  # この関数を使用すれば 16 バイトに満たない場合にも、残りのデータをすべて出力する。
  def flush
    output if 0 < @seqBytesSize
  end

  # 16 バイト分出力可能ならば、出力する。
  def flush16
    output if 16 <= @seqBytesSize
  end

  private
  # 実際の出力処理
  def output
    hex = OutputBuffer.new # 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    str = OutputBuffer.new # ................
    count = 0
    while 0 < @seq.size
      letter = @seq.shift
      
      # 現在の行と次の行にまたがる文字の処理
      if 16 < count + letter.bytes.size
        pos = 16 - count
        # seq.push_front
        @seq.unshift(Letter.new(letter.bytes[pos...letter.bytes.size],
                                0, 0, :breakedLetter));
      end

      # 文字色の決定
      case letter.type
      when :letter
        hex.setColor!($TTYC_LETTER)
        str.setColor!($TTYC_LETTER)
      when :nonLetter
        hex.setColor!($TTYC_NON_LETTER)
        str.setColor!($TTYC_NON_LETTER)
      when :breakedLetter
        hex.setColor!($TTYC_BREAKED_LETTER)
        str.setColor!($TTYC_BREAKED_LETTER)
      when :isoSeq
        hex.setColor!($TTYC_ISO_SEQ)
        str.setColor!($TTYC_ISO_SEQ)
      end

      # ASCIIダンプ作成
      if !$useColor && letter.type == :nonLetter
        (0...letter.strWidth).each{|i| str.add7BitStr!(".") }
      elsif !$useColor && letter.type == :isoSeq
        letter.bytes.each{|byte|
          if byte < 0x1f
            str.add7BitStr!(".")
          else
            str.add7BitStr!([ byte ].pack("C"));
          end
        }
      else
        str.addUnicode!(letter.unicode, letter.strWidth)
      end
      breakedLetterStart = count + letter.strWidth
      (breakedLetterStart...(count + letter.bytes.size)).each{|i|
        if i < 16
          str.setColor!($TTYC_BREAKED_LETTER)
          str.add7BitStr!(".")
        end
      }

      # 16進ダンプ作成
      while count < 16 && 0 < letter.bytes.size
        hex.setColor!($TTYC_BREAKED_LETTER) if count == breakedLetterStart
        hex.add7BitStr!(sprintf("%02x", letter.bytes.shift))
        if count == 3 || count == 7 || count == 11
          hex.add7BitStr!("  ")
        elsif count != 15
          hex.add7BitStr!(" ")
        end
        count += 1
      end
      break if 16 <= count
    end

    if count < 16
      (count...16).each{|i|
        if i == 3 || i == 7 || i == 11
          hex.add7BitStr!("    ")
        elsif i != 15
          hex.add7BitStr!("   ")
        else
          hex.add7BitStr!("  ")
        end
      }
    end

    # 出力
    print $TTYC_NORMAL if $useColor && !$outputAsHTML
    printf("%08x  %s  %s", @address, hex.str, str.str)
    print $TTYC_NORMAL if $useColor && !$outputAsHTML
    print "\n";
    @address += 16

    # @seqBytesSize を計算しなおす
    @seqBytesSize = 0
    @seq.each{|letter|
      @seqBytesSize += letter.bytes.size
    }
  end
end

###############################################################################
# 無限 ungetc をすることが可能な IO クラス

class UndoableIO
  # 既存の io クラスを与えてインスタンスを作成する。
  # io は強制的に binmode にされる。
  def initialize(io)
    @io = io
    @io.binmode
    @undoBuffer = []
  end

  # 1 バイト得る。
  def getc
    if @undoBuffer.size == 0
      return @io.getc
    else
      return @undoBuffer.shift
    end
  end
  
  # 1 バイト戻す。
  def ungetc(c)
    @undoBuffer.unshift(c)
  end

  # a Array に含まれるバイトをすべて戻す。
  def ungets(a)
    @undoBuffer = a + @undoBuffer
  end

  # イテレータ
  def each_byte
    while byte = getc
      yield(byte)
    end
  end
end

###############################################################################

# ISO-2022 の構造のため、また、歴史的な事情により文字集合切り替えシーケンスは
# 一つの文字集合に対して複数存在する。その違いを吸収するためのデータを表すクラス。
class IsoSeq
  def initialize(input, isoSeq, csType)
    @input = input.unpack('C*')
    @isoSeq = isoSeq
    @csType = csType
  end
  # 入力バイト列。
  attr_reader :input
  # ISO-2022 の文字集合切り替えシーケンス。入力バイト列とは異なる場合がある。
  attr_reader :isoSeq
  # 以下の何れか
  # [:ascii] ASCII
  # [:jisRoman] JIS X 0201-1976 (Roman)
  # [:jisKana] JIS X 0201-1976 (Kana)
  # [:x94x94] 漢字など
  # [:asciiOrJisRoman] :ascii か :jisRoman の何れか
  attr_reader :csType
end

# http://www.ietf.org/rfc/rfc1468.txt
# http://www.ietf.org/rfc/rfc2237.txt
#                                                    ; (  Octal,  Decimal.)
#   ESC                 = <ISO 2022 ESC, escape>     ; (    033,       27.)
#   SI                  = <ISO 2022 SI, shift-in>    ; (    017,       15.)
#   SO                  = <ISO 2022 SO, shift-out>   ; (    016,       14.)
#   CR                  = <ASCII CR, carriage return>; (    015,       13.)
#   LF                  = <ASCII LF, linefeed>       ; (    012,       10.)
#   one-of-94           = <any one of 94 values>     ; (041-0176, 33.-126.)
#   7BIT                = <any 7-bit value>          ; (  0-0177,  0.-127.)

$ISO_SEQ_TABLE =
  [
   # [ 入力シーケンス, 文字集合切り替えシーケンス ]

   # ASCII			ESC ( B
   IsoSeq.new("\033(B", "\033(B", :ascii),

   # JIS X 0201-1976 (Roman)	ESC ( J
   IsoSeq.new("\033(J", "\033(J", :jisRoman),
   IsoSeq.new("\033H" , "\033(J", :jisRoman), # NEC JIS
   IsoSeq.new("\033(H", "\033(J", :jisRoman), # 過去に間違って使用されていた (現在はスェーデン語)

   # JIS X 0201-1976 (Kana)	ESC ( I
   IsoSeq.new("\033(I", :jisKana, :jisKana),

   # JIS X 0208-1978		ESC $ @
   IsoSeq.new("\033$@", "\033$@", :x94x94),
   IsoSeq.new("\033K", "\033$@", :x94x94), # NEC JIS
   
   # JIS X 0208-1983		ESC $ B
   IsoSeq.new("\033$B", "\033$B", :x94x94),
   IsoSeq.new("\033$(B", "\033$B", :x94x94), # 謎

   # JISX 0212-1990		ESC $ ( D
   IsoSeq.new("\033$(D", "\033$(D", :x94x94),
  ]
if $useSISO
  $ISO_SEQ_TABLE.concat [
                         # ^N (shift-out) で半角カナ開始
                         IsoSeq.new("\016", :jisKana, :jisKana),
                         # ^O (shift-in) で半角カナ終了
                         IsoSeq.new("\017", "\033(J", :asciiOrJisRoman),
                        ]
end

# ISO-2022-JP 系入力を分解する関数。
#
# ISO-2022-JP 系文字列にならない文字や 8 ビット文字を見つけた時に
# ISO-2022-JP 系文字列は終了したとみなし、この関数から return する。
# この関数は inputSHIFT_JIS, inputEUCJP, inputUTF8 の中から
# 呼ばれることを想定している。
#
# uio は UndoableIO のインスタンス。
# show は Show のインスタンス。
# input7bitType は :ascii か :jisRoman。
def input_sub_ISO_2022_JP_1(uio, show, input7bitType)
  # 文字集合が切り替わっているかどうか調べ、
  # 現在の文字集合テーブルを返す。
  # 切り替わっていなければ nil を返す
  checkCS = lambda{
    bytes = [ uio.getc ]
    if bytes[0] == 033 || bytes[0] == 016 || bytes[0] == 017
      retval = nil
      $ISO_SEQ_TABLE.each{|isoSeq|
        if isoSeq.input.size < bytes.size
          (isoSeq.input.size...bytes.size).each{|i|
            uio.ungetc(bytes.pop)
          }
        elsif bytes.size < isoSeq.input.size
          (bytes.size...isoSeq.input.size).each{|i|
            bytes.push(uio.getc)
          }
        end
        if isoSeq.input == bytes
          show.isoSeq(bytes)
          retval = isoSeq
          break
        end
      }
      return retval if retval
    end
    uio.ungets(bytes)
    nil
  }

  while true
    while nextIsoSeq = checkCS.call
      isoSeq = nextIsoSeq
    end
    return if !isoSeq
    return if (isoSeq.csType == input7bitType ||
               isoSeq.csType == :asciiOrJisRoman)
    
    bytes = [ ]

    case isoSeq.csType
      
    when :jisKana               # 半角カタカナ
      byte = uio.getc
      bytes.push(byte)
      if byte == nil || !(0x21 <= byte && byte <= 0x5f)
        uio.ungetc(byte)
        return
      end
      unicode = to_unicode('CP932', [ byte | 0x80 ].pack('C'))
      
    when :ascii                 # ASCII
      byte = uio.getc
      bytes.push(byte)
      if byte == nil || 0x80 <= byte
        uio.ungetc(byte)
        return
      end
      unicode = byte

    when :jisRoman              # JIS ROMAN
      byte = uio.getc
      bytes.push(byte)
      if byte == nil || 0x80 <= byte
        uio.ungetc(byte)
        return
      end
      unicode = to_unicode('ISO-2022-JP', bytes.pack('C'))
      
    when :x94x94
      bytes.push(uio.getc)
      bytes.push(uio.getc)
      if !(bytes[0] && bytes[1] &&
           041 <= bytes[0] && bytes[0] <= 0176 &&
           041 <= bytes[1] && bytes[1] <= 0176)
        uio.ungets(bytes)
        return
      end
      begin
        unicode = to_unicode($iso2022jp1, isoSeq.isoSeq+bytes.pack('C*'))
      rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
        uio.ungets(bytes)
        return
      end

    else
      uio.ungets(bytes)
      return
      
    end

    show.unicodeLetter(bytes, unicode)
    
  end
end

###############################################################################

# byteCount バイトの UTF-8 が表現可能な UNICODE のビット数
def utf8BitCount(byteCount)
  if byteCount == 1
    return 7
  else
    return 6 * (byteCount - 1) + 7 - byteCount
  end
end

# utf8 入力を分解する関数。
# uio は UndoableIO のインスタンス。
def inputUTF8(uio)
  show = Show.new

  processUTF8 = lambda{|firstByte, utf8Size|
    bytes = [ firstByte ]
    unicode = firstByte & (0b11111111 >> (utf8Size + 1))
    (1...utf8Size).each{|i|
      nextByte = uio.getc
      bytes.push(nextByte)
      if !nextByte || (nextByte & 0b11000000) != 0b10000000
        # utf-8 を構成しない文字か、ファイル終了の場合
        show.nonLetter([ firstByte ], ?.)
        uio.ungets(bytes[1...bytes.size])
        bytes = nil
        break
      end
      unicode = (unicode << 6) | (nextByte & 0b00111111)
    }
    if bytes
      if (1 << utf8BitCount(utf8Size - 1)) <= unicode
        show.unicodeLetter(bytes, unicode)
      else
        # utf-8 を構成しない文字だった
        show.nonLetter([ firstByte ], ?.)
        uio.ungets(bytes[1...bytes.size])
      end
    end
  }

  while true
    input_sub_ISO_2022_JP_1(uio, show, :ascii)
    byte = uio.getc
    break if !byte
    bytes = [ byte ]
    if byte <= 0x7f
      show.unicodeLetter(bytes, byte)
    elsif (byte & 0b11100000) == 0b11000000
      processUTF8.call(byte, 2)
    elsif (byte & 0b11110000) == 0b11100000
      processUTF8.call(byte, 3)
    elsif (byte & 0b11111000) == 0b11110000
      processUTF8.call(byte, 4)
    else
      show.nonLetter([ byte ], ?.)
    end
  end
  show.flush
end

###############################################################################
# UTF-16 入力を分解する関数。
# uio は UndoableIO のインスタンス。
# isLE が真の時は UTF-16LE, 偽の時は UTF-16BE。

def inputUTF16(uio, isLE)
  show = Show.new
  
  if $alignedUTF16              # 2バイト境界に整列している場合
    prev = nil
    uio.each_byte{|byte|
      if prev == nil
        prev = byte
      else
        bytes = [ prev, byte ]
        if isLE
          show.unicodeLetter(bytes, (byte << 8) + prev)
        else
          show.unicodeLetter(bytes, (prev << 8) + byte)
        end
        prev = nil
      end
    }
    show.nonLetter([ prev ], ?.) if prev != nil

  else                          # 2バイト境界に整列していない場合
    while true
      bytes = [ ]
      bytes.push(uio.getc)
      break if !bytes[0]
      bytes.push(uio.getc)
      if !bytes[1]
        show.nonLetter([ bytes[0] ], ?.)
        break;
      end
      unicode = isLE ? (bytes[0] | (bytes[1] << 8)) :
        ((bytes[0] << 8) | bytes[1])
      utf16bePacked = [ unicode ].pack('n')

      if (unicode < 0x20 || unicode == 0x7f ||
          (0xd800 <= unicode && unicode <= 0xdb7f) || # High Surrogates
          (0xdb80 <= unicode && unicode <= 0xdbff) || # High Private Use Surrogates
          (0xdc00 <= unicode && unicode <= 0xdfff) || # Low Surrogates
          (0xe000 <= unicode && unicode <= 0xf8ff)) && # Private Use Area
          unicode != ?\r && unicode != ?\n
        show.nonLetter([ bytes[0] ], ?.)
        uio.ungetc(bytes[1])
        next
      end

      # 出力したい文字コードで出力可能な文字かどうか調査
      begin
        Iconv.conv($outputCS, 'UTF-16BE', utf16bePacked)
      rescue
        show.nonLetter([ bytes[0] ], ?.)
        uio.ungetc(bytes[1])
        next
      end
      
      if !$isOutputCSUTF8
        show.unicodeLetter(bytes, unicode)
        next
      end
      
      # 出力が UTF-8 ならば、日本語文字コードへ変換可能でなければならない
      catch (:next) {
        [ 'CP932', 'SHIFT_JIS', $iso2022jp1, 'EUC-JP' ].each{|cs|
          begin
            Iconv.conv(cs, 'UTF-16BE', utf16bePacked)
            show.unicodeLetter(bytes, unicode)
            throw :next
          rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
          end
        }
        show.nonLetter([ bytes[0] ], ?.)
        uio.ungetc(bytes[1])
      }
    end
    
  end
    
  show.flush
end

###############################################################################
# SHIFT_JIS, CP932 入力を分解する関数。
# uio は UndoableIO のインスタンス。
# isCP932 が真の時は CP932, 偽の時は SHIFT_JIS を取り扱う。

def inputSHIFT_JIS(uio, isCP932)
  show = Show.new
  cs = isCP932 ? 'CP932' : 'SHIFT_JIS'
  while true
    input_sub_ISO_2022_JP_1(uio, show, isCP932 ? :ascii : :jisRoman)
    
    byte = uio.getc
    break if !byte
    
    if byte < 0x80 || (0xa0 <= byte && byte < 0xe0)
      begin
        show.unicodeLetter([ byte ], to_unicode(cs, [ byte ].pack('C')))
      rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
        show.nonLetter([ byte ], ?.)
      end
    else
      nextByte = uio.getc
      if !nextByte
        show.nonLetter([ byte ], ?.)
      else
        bytes = [ byte, nextByte ]
        begin
          show.unicodeLetter(bytes, to_unicode(cs, bytes.pack('C*')))
        rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
          show.nonLetter([ byte ], ?.)
          uio.ungetc(nextByte)
        end
      end
    end
  end
  show.flush
end

###############################################################################
# EUC-JP 入力を分解する関数。
# uio は UndoableIO のインスタンス。

def inputEUCJP(uio)
  show = Show.new
  while true
    input_sub_ISO_2022_JP_1(uio, show, :ascii)

    bytes = [  ]
    bytes.push(uio.getc)

    break if !bytes[0]

    if bytes[0] < 0x80
      show.unicodeLetter(bytes, bytes[0])
      
    elsif (bytes[0] == 0x8e ||   # 半角カナ
           0xa1 <= bytes[0])     # JIS X 0208-1990
      bytes.push(uio.getc)
      if !bytes[1]
        show.nonLetter([ bytes[0] ], ?.)
      else
        begin
          show.unicodeLetter(bytes, to_unicode('EUC-JP', bytes.pack('C*')))
        rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
          show.nonLetter([ bytes[0] ], ?.)
          uio.ungetc(bytes[1])
        end
      end
      
    elsif bytes[0] == 0x8f       # JIS X 0212-1990
      bytes.push(uio.getc)
      if !bytes[1]
        show.nonLetter([ bytes[0] ], ?.)
      else
        bytes.push(uio.getc)
        if !bytes[2]
          show.nonLetter([ bytes[0], bytes[1] ], ?.)
        else
          begin
            show.unicodeLetter(bytes, to_unicode('EUC-JP', bytes.pack('C*')))
          rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
            show.nonLetter([ bytes[0] ], ?.)
            uio.ungetc(bytes[2])
            uio.ungetc(bytes[1])
          end
        end
      end
      
    end
  end
  show.flush
end

###############################################################################
# 文字コードを考慮せず入力を解釈する関数。
# uio は UndoableIO のインスタンス。

def inputUnknown(uio)
  show = Show.new
  uio.each_byte{|byte|
    if byte <= 0x1f
      show.nonLetter([byte], byte + ?@)
    elsif byte == 0x7f
      show.nonLetter([byte], ??)
    elsif byte < 0x7f
      show.letter([byte], byte, 1)
    else
      show.nonLetter([byte], ?.)
    end
  }
  show.flush
end

###############################################################################
# hexja の出力形式をバイナリへ変換する。
#  % hexja < foo | hexja -r > bar
# は
#  % cat < foo > bar
# と同じ結果になるようにする。
def restore(file)
  $stdout.binmode
  file.each_line{|line|
    next if line !~ /^[0-9a-f]{8}  ((([0-9a-f ]{2} ){4} ){4})/i
    $1.split(/\s+/).each{|i|
      $stdout.write([ i.hex ].pack('C'))
    }
  }
end

###############################################################################
# 入力文字コードに従って適切な入力分解関数を呼ぶ。

def input(file)
  if $restoreMode
    restore(file)
    return
  end

  if $outputAsHTML == :css && $useColor
    print <<"__EOM__"
<html>
<style type="text/css">
__EOM__
    print <<"__EOM__" if $HTML_TTYC_LETTER
.hexja .letter         { color:#{$HTML_TTYC_LETTER}; }
__EOM__
    print <<"__EOM__"
.hexja .non-letter     { color:#{$HTML_TTYC_NON_LETTER}; }
.hexja .breaked-letter { color:#{$HTML_TTYC_BREAKED_LETTER}; }
.hexja .iso-seq        { color:#{$HTML_TTYC_ISO_SEQ}; }
</style>
<body>
<pre class="hexja">
__EOM__
  elsif $outputAsHTML
    print <<"__EOM__"
<pre>
__EOM__
  end

  uio = UndoableIO.new(file)
  if !$inputCS
    inputUnknown(uio)
  elsif $inputCS =~ /UTF-?8/i
    inputUTF8(uio)
  elsif $inputCS =~ /UTF-?16-?le/i
    inputUTF16(uio, true)
  elsif $inputCS =~ /UTF-?16-?be/i
    inputUTF16(uio, false)
  elsif $inputCS =~ /UTF-?16/i
    inputUTF16(uio, true)
  elsif $inputCS =~ /euc/i
    inputEUCJP(uio)
  elsif $inputCS =~ /sjis|shift.?jis/i
    inputSHIFT_JIS(uio, false)
  elsif $inputCS =~ /cp932/i
    inputSHIFT_JIS(uio, true)
  else
    print "入力文字コードが間違っています: #{$inputCS}\n";
    exit 1
  end
  
  if $outputAsHTML == :css && $useColor
    print <<"__EOM__"
</pre>
</body>
</html>
__EOM__
  elsif $outputAsHTML
    print <<"__EOM__"
</pre>
__EOM__
  end
  
end

###############################################################################
# メイン

begin
  if argv.size == 0
    input($stdin)
  else
    argv.each{|filename|
      input(open(filename))
    }
  end
rescue Errno::EPIPE
  # 出力の Broken pipe シグナルを捕捉して無視する
end
