タイトルの付け方

入力方法

タイトルの入力方法には2種類あります。投稿画面タイトル欄の右のアイコンをクリックすることで入力方法を切り替えることができます。 タイトルの各部を入力する方法と、1行ですべてを入力する方法の2種類になりますが、どちらを使用しても投稿時にコンパイルされるため、ほぼ同じ結果となります。 タイトルのフォーマットに自信がない方は各部を入力するようにして下さい。タイトルの各部をタイトルコンポーネントといい、次のように分類されます。 なお、タイトルの最大文字数は256文字となります。

著作者 著作者のフルネームを記載します。苗字と名前の間にスペースは入れず、複数人入力する場合は半角カンマ(,) 半角x, または全角「×」のいずれかを使用して区切って下さい。 雑誌など、著作者が不特定多数または未定義の場合は空白にしてください。
書籍名 書籍のタイトルを正確に記載して下さい。漢字ミスなど、検索結果に正しく表示されない状態は好ましくありません。 タイトルは基本的にWikipediaを参考にし、ビックリマークやクエスチョンマークおよび英数字は半角文字を使用して下さい。
巻数 該当する巻数を半角数字で入力してください。巻数は2桁で0埋めしますが、3桁以上の巻が含まれる場合は3桁で統一して下さい。 タイトルコンポーネントでの入力は自動的に0埋めされます。複数巻がある場合、半角ハイフン(-)または半角カンマ(,)で区切って下さい。 「第01-03巻」や「第01,03-05巻」などの表記が適切です。単に数字だけを入力した場合、「第n巻」というように日本語部分が補完されます。 その他想定しているフォーマットとしては、「全n巻」や「第n集」などがあります。 雑誌の場合、単に数字だけを入力すると年号が補完されます。年号を指定することも可能です。 「2015年10月号」や「2015年07-08号」などが適切です。同人誌や単巻の書籍は空白にして下さい。
オプション 「ページ抜け補完」や「Completed」など、投稿内容を説明する単語を入力することができますが、長い文章は投稿者コメント欄に入力して下さい。

フォーマット

タイトルのフォーマットはカテゴリによって異なります。各部の詳細なフォーマットは上の表に記載の通りです。一般的な書籍のタイトル形式は次のとおりです。 各部の間は半角スペースで区切って下さい。後ろに「Complete」などの情報を付け加えることができます。

[著作者] タイトル 第01巻

雑誌のフォーマットは次の通りです。

雑誌名 2015年01号

著作者名

著作者名はフルネームで記載し、日本語表記の場合は苗字と名前の間にスペースを空けないで下さい。 原則として一般漫画、少女漫画、小説、やおい漫画、やおい小説、成年指定漫画には1人以上の著作者名を記入する必要があります。 作画担当や原作の著作者名などを複数記載することもできます。 著作者名が複数になる場合、「x」「x」「X」「X」「×」「,」のいずれかの文字を使用して区切って下さい。

正規表現

タイトルの分解には次の正規表現が用いられます。PHPでの例です。グループマッチはそれぞれ 2番目が著作者, 3番目が書籍名, 6番目が巻数, 13番目がオプション となります。

/^(\(.+?\))? ?\[(.+?)\] ?(.+?) ((第|全)?(([0-9]{1,4}年)?([0-9]{1,4}(-[0-9]{1,4})?)( ?, ?[0-9]{1,4}(-[0-9]{1,4})?)*月?)(巻|号|集))? ?(.*?)$/ui

また、各タイトルコンポーネントは次のフィルタを用いて変換されます。PHPでの例です。composeメソッドの返り値が実際のタイトルとして使用されます。 上記の正規表現を用いたタイトルの分解にはdecomposeメソッドが使用されます。

class ArticleTitleService {
    
    const AuthorSeparator = "x";
    const VolumeDefaultDecimals = 2;
    
    private $category;
    private $title;
    private $author;
    private $rawTitle;
    private $volume;
    private $option;
    
    public function __construct($category) {
        if(!$category){
            throw new InvalidArgumentException;
        }
        $this->category = $category;
    }
    
    public function update() {
        $args = func_get_args();
        if(count($args) == 1){
            $this->title = trim($args[0]);
        }
        else if(count($args) == 4){
            $this->author = trim($args[0]);
            $this->rawTitle = trim($args[1]);
            $this->volume = trim($args[2]);
            $this->option = trim($args[3]);
        }
        else{
            throw new InvalidArgumentException;
        }
    }
    
    public function compose() {
        return $this->title = trim(join(" ", array(
            $this->composeAuthor(), $this->composeRawTitle(),
            $this->composeVolume(), $this->composeOption(),
        )));
    }
    
    private function composeAuthor() {
        $author = $this->author;
        if(preg_match("/^\[([^\[\]]+)\]$/", $this->author, $m)){
            $author = trim($m[1]);
        }
        if($author){
            return "[".str_replace(array("×",","), self::AuthorSeparator, $author)."]";
        }
        return "";
    }

    private function composeRawTitle() {
        return $this->rawTitle;
    }

    private function composeVolume() {
        if(!trim($this->volume)) return "";
        $volume = $this->commonConvert(trim($this->volume));
        if(in_array($this->category, array("etc", "doujin"))){
            return $volume;
        }
        if($this->category == "magazine" && preg_match("/^([0-9]{4}(年|-|\/))?([0-9\-, ]+)(月?号)?$/", $volume, $m)){
            $year = $m[1] ? str_replace("/", "年", $m[1]) : date("Y年");
            return $year.$this->formatVolume($m[3]).(isset($m[4]) && $m[4] ? $m[4] : "号");
        }
        if(preg_match("/^(第|全)?([0-9\-, ]+)(巻|集)?$/", $volume, $m)){
            return (isset($m[1]) && $m[1] ? $m[1] : "第").$this->formatVolume($m[2]).(isset($m[3]) && $m[3] ? $m[3] : "巻");
        }
        return $volume;
    }

    private function composeOption() {
        return $this->option;
    }
    
    public function decompose($title = null) {
        if(!$title){
            if($this->title){
                $title = $this->title;
            }
            else{
                throw new InvalidArgumentException;
            }
        }
        $title = $this->commonConvert($title);
        if(preg_match("/^(\(.+?\))? ?\[(.+?)\] ?(.+?) ((第|全)?(([0-9]{1,4}年)?([0-9]{1,4}(-[0-9]{1,4})?)".
                      "( ?, ?[0-9]{1,4}(-[0-9]{1,4})?)*月?)(巻|号|集))? ?(.*?)$/ui", $title, $m)){
            $this->author = trim($m[2]);
            $this->rawTitle = trim($m[3]);
            $this->volume = trim($m[6]);
            $this->option = trim($m[13]);
        }
        else{
            $this->author = "";
            $this->rawTitle = $this->title;
            $this->volume = "";
            $this->option = "";
        }
        return array(
            "author" => $this->author,
            "rawTitle" => $this->rawTitle,
            "volume" => $this->volume,
            "option" => $this->option,
        );
    }
    
    private function formatVolume($volume) {
        $volume = str_replace(" ", "", $volume);
        $elems = preg_split("/[\-,]+/", $volume);
        $seps = Util::removeEmpty(preg_split("/[0-9]+/", $volume));
        $maxDecimal = self::VolumeDefaultDecimals;
        $ret = "";
        foreach($elems as $elem){
            $maxDecimal = max(strlen($elem), $maxDecimal);
        }
        foreach($elems as $i => $elem){
            $ret .= str_pad($elem, $maxDecimal, "0", STR_PAD_LEFT).(isset($seps[$i]) ? $seps[$i] : "");
        }
        return $ret;
    }

    private function commonConvert($raw) {
        return str_replace(
            array("0","1","2","3","4","5","6","7","8","9"), 
            array("0","1","2","3","4","5","6","7","8","9")
        , $raw);
    }
    
}