PHPで下記のように記述すると、HTMLのリスト(ul ol)として出力させたいのですが、うまくいかなくて困っています。


記述方法は、
1. 通常のリストは「-」、番号付きリストは「+」を行頭に付ける
2. 更に行頭に1つ以上の半角スペースを付けると、入れ子構造になる(入れ子の階層に制限なし)

となり、問題なく動作させるにはどうしたらよいでしょうか?

できればソース付きで回答して頂くと大変助かります。
よろしくお願いします。

■入力テキスト
-リスト1
-リスト2
 +番号付きリスト2-A
 +番号付きリスト2-B
  -リスト3-A
  -リスト3-B
 +番号付きリスト2-C
-リスト3

■出力結果
<ul>
<li>リスト1</li>
<li>リスト2
  <ol>
  <li>番号付きリスト2-A</li>
  <li>番号付きリスト2-B
    <ul>
    <li>リスト3-A</li>
    <li>リスト3-B</li>
    </ul>
  </li>
  <li>番号付きリスト2-C</li>
  </ol>
</li>
<li>リスト3</li>
</ul>

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2013/04/30 07:47:55
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:GoldenDawn No.2

回答回数426ベストアンサー獲得回数81

ポイント150pt
<?php
$input = <<< ENDOFINPUT
-リスト1
-リスト2
 +番号付きリスト2-A
 +番号付きリスト2-B
  -リスト3-A
  -リスト3-B
 +番号付きリスト2-C
-リスト3
ENDOFINPUT;

$lines = preg_split('/[\r\n]+/', $input) ;

// リストのタグを全て閉じる
function close_lists($d) {
  while ($l = array_pop($d)) {
    echo "</li>\n".
         str_repeat(' ', count($d)*4). // インデント調整
         ($l[1] == '-' ? '</ul>' : '</ol>') ;
  }
  echo "\n" ;
  return array() ;
}

$d = array() ; // 現在の階層を保存

while ($l = array_shift($lines)) {
  if (preg_match('/^( *)([-+])(.*)$/', $l, $m)) {
    $e = end($d) ; // 現在の階層

    // インデントが浅いか種類が変わればタグを閉じる
    while ($d && ($e[0] > $m[1] || ($e[0] == $m[1] && $e[1] != $m[2]))) {
      array_pop($d) ;
      echo "</li>\n".
           str_repeat(' ', count($d)*4). // インデント調整
           ($e[1] == '-' ? '</ul>' : '</ol>') ;
      $e = end($d) ;
    }

    // 階層を増やす
    if (!count($d) || $e[0] < $m[1]) {
      $d[] = array($m[1], $m[2]) ;
      if ($e) echo "\n" ;
      echo str_repeat(' ', (count($d)-1)*4). // インデント調整
           ($m[2] == '-' ? '<ul>' : '<ol>')."\n" ;
    }
    else echo "</li>\n" ;

    // リストの要素
    echo str_repeat(' ', count($d)*4-2). // インデント調整
         '<li>'.$m[3] ;
  }
  // リストの記号がない
  else {
    $d = close_lists($d) ;
    echo "$l\n" ;
  }
}

close_lists($d) ;
?>

出力例

<ul>
  <li>リスト1</li>
  <li>リスト2
    <ol>
      <li>番号付きリスト2-A</li>
      <li>番号付きリスト2-B
        <ul>
          <li>リスト3-A</li>
          <li>リスト3-B</li>
        </ul></li>
      <li>番号付きリスト2-C</li>
    </ol></li>
  <li>リスト3</li>
</ul>
id:wankodon

遅れましたが、シンプルな実装でとても勉強になります。
こんなやり方があったとは。回答どうも有り難うございました。

2013/04/30 07:46:56

その他の回答1件)

id:dawakaki No.1

回答回数797ベストアンサー獲得回数122

ポイント150pt

どの行にも + か - があるものという前提です。

<?php
mb_internal_encoding('utf-8');

$text =<<< EOT
-リスト1
-リスト2
 +番号付きリスト2-A
 +番号付きリスト2-B
  -リスト3-A
  -リスト3-B
 +番号付きリスト2-C
-リスト3

EOT;

function text2list($text) {
    $data = explode("\n", $text);
    $n = count($data);
    $output = '';
    $level = -1;
    for ($i = 0; $i < $n; $i++) {
        $pos = strpos($data[$i], '+');
        if ($pos == FALSE) {
            $pos = strpos($data[$i], '-');
        }
        if ($pos < $level) {
            for ($j = $level; $j > $pos; $j--) {
                $output .= '</' . $tag[$level] . ">\n";
            }
            $output .= '<li>' . substr($data[$i], $pos + 1) . "</li>\n";
            $level = $pos;
        } else if ($pos > $level) {
            $ch = substr($data[$i], $pos, 1);
            if ($ch == '+')   $c = 'ol';
            else              $c = 'ul';
            for ($j = $level + 1; $j <= $pos; $j++) {
                $tag[$j] = $c;
                $output .= '<' . $tag[$j] . ">\n";
            }
            $output .= '<li>' . substr($data[$i], $pos + 1) . "</li>\n";
            $level = $pos;
        } else if ($data[$i] == '') {
            $output .= '</' . $tag[0] . ">\n";
        } else {
            $output .= '<li>' . substr($data[$i], $pos + 1) . "</li>\n";
        }
    }
    return $output;
}

$output = text2list($text);
echo $output;
?>
id:wankodon

遅れましたが、回答有り難うございました。
参考にして色々試したいと思います。

2013/04/30 07:40:50
id:GoldenDawn No.2

回答回数426ベストアンサー獲得回数81ここでベストアンサー

ポイント150pt
<?php
$input = <<< ENDOFINPUT
-リスト1
-リスト2
 +番号付きリスト2-A
 +番号付きリスト2-B
  -リスト3-A
  -リスト3-B
 +番号付きリスト2-C
-リスト3
ENDOFINPUT;

$lines = preg_split('/[\r\n]+/', $input) ;

// リストのタグを全て閉じる
function close_lists($d) {
  while ($l = array_pop($d)) {
    echo "</li>\n".
         str_repeat(' ', count($d)*4). // インデント調整
         ($l[1] == '-' ? '</ul>' : '</ol>') ;
  }
  echo "\n" ;
  return array() ;
}

$d = array() ; // 現在の階層を保存

while ($l = array_shift($lines)) {
  if (preg_match('/^( *)([-+])(.*)$/', $l, $m)) {
    $e = end($d) ; // 現在の階層

    // インデントが浅いか種類が変わればタグを閉じる
    while ($d && ($e[0] > $m[1] || ($e[0] == $m[1] && $e[1] != $m[2]))) {
      array_pop($d) ;
      echo "</li>\n".
           str_repeat(' ', count($d)*4). // インデント調整
           ($e[1] == '-' ? '</ul>' : '</ol>') ;
      $e = end($d) ;
    }

    // 階層を増やす
    if (!count($d) || $e[0] < $m[1]) {
      $d[] = array($m[1], $m[2]) ;
      if ($e) echo "\n" ;
      echo str_repeat(' ', (count($d)-1)*4). // インデント調整
           ($m[2] == '-' ? '<ul>' : '<ol>')."\n" ;
    }
    else echo "</li>\n" ;

    // リストの要素
    echo str_repeat(' ', count($d)*4-2). // インデント調整
         '<li>'.$m[3] ;
  }
  // リストの記号がない
  else {
    $d = close_lists($d) ;
    echo "$l\n" ;
  }
}

close_lists($d) ;
?>

出力例

<ul>
  <li>リスト1</li>
  <li>リスト2
    <ol>
      <li>番号付きリスト2-A</li>
      <li>番号付きリスト2-B
        <ul>
          <li>リスト3-A</li>
          <li>リスト3-B</li>
        </ul></li>
      <li>番号付きリスト2-C</li>
    </ol></li>
  <li>リスト3</li>
</ul>
id:wankodon

遅れましたが、シンプルな実装でとても勉強になります。
こんなやり方があったとは。回答どうも有り難うございました。

2013/04/30 07:46:56
  • id:TransFreeBSD
    答えれるか分からないのですが、疑問点
    いかはどうするのか?

    1. いきなり空白付きだった場合
     +レベル2
    +レベル1
    みたいなとき

    2.空白が一気に増えた場合
    +レベル1
      +レベル3
    みたいなとき

    3.番号なし・付きが両方ある場合
    +レベル1の1
    -レベル1の・
    みたいなとき
  • id:wankodon
    コメント有り難うございます。
    ご質問頂いた点を以下に箇条書きで失礼します。

    >1. いきなり空白付きだった場合

    入れ子が解除され、+レベル2は+レベル1扱いになります(先頭の空白がない状態に)

    >2.空白が一気に増えた場合

    前行を参考に入れ子の階層を決定します。
    (レベル1からレベル3に飛ばすことはできない)

    例えば、
    +レベル1
         +レベル3

    +レベル1
     +レベル2

    と同じ階層になります。

    >3.番号なし・付きが両方ある場合

    一旦、タグを閉じることになります。

    +レベル1
    -レベル1

    なら以下のように出力します。

    <ul>
     <li>リスト1</li>
    </ul>
    <ol>
     <li>リスト2</li>
    </ol>

    以上になります。
    矛盾がありましたら指摘して頂ければと思います。

    実装するのは骨が折れるかと思いますが。。
    よろしくお願いします。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません