Как вывести многоколоночный HTML без "вдов"?

Мне нужно вывести в HTML список категоризированных ссылок ровно в трех столбцах текста. Они должны отображаться аналогично колонкам в газете или журнале. Так, например, если всего 20 строк, первый и второй столбцы будут содержать 7 строк, а последний столбец будет содержать 6. Список должен быть динамическим; это будет регулярно меняться.

Сложность в том, что ссылки классифицированы с заголовком, и этот заголовок не может быть "вдовой". Если у вас есть фон макета страницы, вы будете знать, что это означает, что заголовки не могут отображаться внизу столбца - у них должна быть хотя бы одна ссылка под ними, в противном случае они должны перейти к следующему столбцу (я знаю, технически это должно быть две строки, если я на самом деле делал макет страницы, но в этом случае одна из них приемлема). Мне трудно понять, как это сделать.

Вот пример того, что я имею в виду:

Шоппинг Ссылка 3 Ссылка 1
Ссылка 1 ссылка 4 ссылка2
Ссылка 2 Ссылка 3
Link 3       Cars
             Ссылка 1 Музыка
Игры Ссылка 2 Ссылка 1
Ссылка 1              
Ссылка 2 Новости

Как видите, заголовок "Новости" находится внизу среднего столбца, как и "вдова". Это неприемлемо. Я мог бы переместить его в следующий столбец, но это создало бы излишне большое количество пустого пространства внизу второго столбца. Вместо этого весь список должен быть перебалансирован.

Мне интересно, есть ли у кого-нибудь советы о том, как это сделать, или, возможно, исходный код или плагин. Python предпочтительнее, но любой язык в порядке. Я просто пытаюсь изложить общую концепцию.

1 ответ

Решение

Основная идея состоит в том, чтобы создать основной список всех "текучих" элементов (включая категории), а затем пройти по списку, корректируя строки в столбце по мере необходимости так, чтобы никакие категории не оставались вдовами (или какими-либо другими условиями, которые у вас могут быть).

Module Module1

    Dim Categories As New Dictionary(Of String, List(Of String))

    Sub Main()

        Const Columns As Integer = 3

        ' create the category items
        Dim ShoppingList As New List(Of String)
        Dim GamesList As New List(Of String)
        Dim CarsList As New List(Of String)
        Dim NewsList As New List(Of String)
        Dim MusicList As New List(Of String)

        ShoppingList.Add("Link1")
        ShoppingList.Add("Link2")
        ShoppingList.Add("Link3")

        GamesList.Add("Link1")
        GamesList.Add("Link2")
        GamesList.Add("Link3")
        GamesList.Add("Link4")

        CarsList.Add("Link1")
        CarsList.Add("Link2")

        NewsList.Add("Link1")
        NewsList.Add("Link2")
        NewsList.Add("Link3")

        MusicList.Add("Link1")

        ' create the categories
        Categories.Add("Shopping", ShoppingList)
        Categories.Add("Games", GamesList)
        Categories.Add("Cars", CarsList)
        Categories.Add("News", NewsList)
        Categories.Add("Music", MusicList)

        ' count each category and its items
        Dim TotalRows As Integer = Categories.Count
        For Each kvp As KeyValuePair(Of String, List(Of String)) In Categories
            TotalRows += kvp.Value.Count
        Next

        ' add a space between each category
        TotalRows += (Categories.Count - 1)

        ' determine the number of rows per column
        Dim RowsPerColumn As Integer = Int(TotalRows / Columns) + If((TotalRows Mod Columns) > 0, 1, 0)

        ' build a master list
        Dim master As New List(Of String)
        For Each kvp As KeyValuePair(Of String, List(Of String)) In Categories
            master.Add(kvp.Key)
            For Each item As String In kvp.Value
                master.Add(item)
            Next
            master.Add(" ")
        Next

        ' remove the last invalid blank item
        master.RemoveAt(master.Count - 1)

        ' ensure that the RowsPerColumn'th-item in the list is not a category
        Dim adjusted As Boolean
        Do
            adjusted = False
            For i As Integer = 1 To master.Count - 1 Step RowsPerColumn - 1
                If Categories.Keys.Contains(master(i)) Then
                    RowsPerColumn += 1 ' adjust rows per column (could go up or down)
                    adjusted = True
                End If
            Next
        Loop While adjusted

        ' output resulting table
        Using sw As New IO.StreamWriter("test.htm")
            sw.WriteLine("<html>")
            sw.WriteLine("<body>")
            sw.WriteLine("<table cellspacing=""0"" cellpadding=""3"" border=""1"">")
            For j As Integer = 0 To RowsPerColumn - 1
                sw.WriteLine("<tr>")
                Dim columnCount As Integer = 0 ' columns written
                For i As Integer = j To master.Count - 1 Step RowsPerColumn
                    sw.WriteLine("<td>" & master(i) & "</td>")
                    columnCount += 1
                Next
                ' if the number of columns actually written was less than Columns constant
                If columnCount < Columns Then
                    For c As Integer = 0 To Columns - columnCount - 1
                        sw.WriteLine("<td>&nbsp;</td>")
                    Next
                End If
                sw.WriteLine("</tr>")
            Next
            sw.WriteLine("</table>")
            sw.WriteLine("</body>")
            sw.WriteLine("</html>")
        End Using

    End Sub

End Module
Другие вопросы по тегам