Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to specify table class with attr_list #312

Closed
jonblack opened this issue May 26, 2014 · 8 comments
Closed

Add support to specify table class with attr_list #312

jonblack opened this issue May 26, 2014 · 8 comments

Comments

@jonblack
Copy link

The bootstrap 3 framework has support for tables, but requires various classes be set on the <table> element: e.g. <table class="table table-bordered table-striped">.

If I try to do this with the attr_list and tables extensions, a new empty row is created and the class is applied to the first <td> element in that row.

For example...

| Tables        | Are           | Cool  |
| ------------- |:-------------:| -----:|
| col 2 is      | centered      |   $12 |
| zebra stripes | are neat      |    $1 |
{: .table table-bordered .table-striped}

...outputs...

<table>
  <thead>
    <tr>
      <th>Tables</th>
      <th align="center">Are</th>
      <th align="right">Cool</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>col 2 is</td>
      <td align="center">centered</td>
      <td align="right">$12</td>
    </tr>
    <tr>
      <td>zebra stripes</td>
      <td align="center">are neat</td>
      <td align="right">$1</td>
    </tr>
    <tr>
      <td table-bordered="table-bordered" class="table table-striped"></td>
      <td align="center"></td>
      <td align="right"></td>
    </tr>
  </tbody>
</table>

Since <table> is a block level element, I would expect the attributes to be applied to there; I certainly don't expect an extra empty row.

@waylan
Copy link
Member

waylan commented May 27, 2014

@jonblack I have two responses to your request.

First, see my position on adding features to the existing table extension as explained in issue #74. My position hasn't changed. In fact, there are a few third-party extensions which implement more feature rich table syntax as listed on the wiki. That said, seems to me like raw html will give you the most power to best meet your needs.

Second, even if I was interested in adding better support for attr_list to the existing table extensions, how would one differentiate between attributes defined on a row, or on a table. The syntax only defines the cells, the rows and table are inferred. The same issue exists with lists as explained in issue #227. My position here is no different that with lists. But again, a third party extension is certainly welcome to offer a solution. That said, raw html is the only solution I'd be interested in.

@waylan waylan closed this as completed May 27, 2014
@jonblack
Copy link
Author

Thanks for the detailed response.

First, see my position on adding features to the existing table extension as explained in issue #74. My position hasn't changed.

I already found that in a response to another issue and agree with your decision not to expand the tables extension.

Second, even if I was interested in adding better support for attr_list to the existing table extensions, how would one differentiate between attributes defined on a row, or on a table.

There are two ways of doing this. The first and most complex way is, from a syntax perspective, shown in the example below:

| Tables                        | Are           | Cool  | {: .header-row }
| ------------------------------|:-------------:| -----:|
| col 2 is {: .table-cell }     | centered      |   $12 | {: .data-row-1 }
| zebra stripes                 | are neat      |    $1 | {: .data-row-2 }
{: .table table-bordered .table-striped}

I'll admit, this is extremely verbose and does remove some of the tidiness you get using markdown-styled tables. At the same time, at least it removes the need for html.

The second way is allow the attr_list plugin to support adding attributes to the <table> tag; users can use css to select the rows and cells. So for a table with the my-custom-table-class (note: my css skills are poor, please don't judge):

table .my-custom-table-class {
  ...
}

table .my-custom-table-class > tr {
  ...
}

I think this offers enough flexibility and I'd assume is trivial to implement.

I see you closed the issue. Hopefully I've given you something to think about. Regardless, thanks for the excellent library.

@nfarrar
Copy link

nfarrar commented Jul 28, 2014

I've just encountered this issue with python-markdown today and I must say - it's insanely frustrating that I can't apply a style inline to a markdown table using attr_list.

After several hours of working with python-markdown forks and python-markdown third party extensions (with nothing working), the only solutions I see left are:

  1. Don't write my tables in markdown because python-markdown won't apply attributes to them.
  2. Learn CSS/less (completely outside of my skillset) so that I can apply bootstrap styling to markdown-generated tables.

I assume that you've not changed your position on this feature, but I'd very much like to +1 this feature request. It seems trivial to implement and highly desired.

@SoftwareMaven
Copy link

So I got here via the <ul> issue being closed and referencing this issue. There, it was suggested that HTML be used, but that is a difficult solution because, once a tag is opened, everything inside has to be converted to HTML. It is less-than-user-friendly to force content authors to use HTML for everything in a list just to apply a style to said list.

I would like to propose a solution that is backwards compatible and handles all levels of nested HTML constructs. The basic idea is to add a new name/value pair to an attribute list: {: ^target=entity-name }. An example would be:

* List item
* Another list item
{: ^target=list .list-class }

This would product a styled list: <ul class="list-class"><li>...</li><li>...</li></ul>.

If the target is left out, the current behavior applies. This is critical to provide backwards compatibility. Now, to handle the case where I want to style list items and the list itself, we add the ability to have multiple attribute lists:

* List item
* Another list item
{: ^target=list .list-class } {: .list-item-class }

is the same as:

* List item
* Another list item
{: ^target=list .list-class } {: ^target=list-item .list-item-class }

Since the default is current behavior, the following should do what you expect:

* List item
{: .list-item-first-class }
* Another list item
{: ^target=list .list-class-2 } {: .list-item-second-class }

For simplicity, if the value gets redefined, it could either be "last definition wins", "additive" or a syntax error. It really doesn't matter, as long as it is defined:

* List item
{: ^target=list .list-class-1 } {: .list-item-first-class }
* Another list item
{: ^target=list .list-class-2 } {: .list-item-second-class }

could end up with <ul class="list-class-1 list-class-2">, <ul class="list-class-2">, or an error. It seems like the easiest implementation would be to make it additive, but I haven't looked at the code yet.

Finally, what happens if you supply a target that doesn't exist?

* List item
* Another list item
{: ^target=body .body-class }

Again, this could either silently ignore such classes or it could be an error. I would lean towards an error so people get notified when something goes wrong.

Tables would behave the same way, except valid targets could be table, row, header, etc. Again, the default behavior would apply the attribute list to the cell.

^target was chosen because it cannot be in current use (it's not valid to begin an attribute with a character like ^) and to imply the target of the attribute is "above" the current level, to make it more semantically meaningful when you look at the list. However, given target is a valid HTML attribute, I could also see the benefit of using something else like ^entity. Also, while using values like list-item, list, cell, etc were chosen for end-user simplicity (they don't have to know HTML syntax to understand the target), it wouldn't be unreasonable to choose the entities themselves (tr, ul, etc) as the targets.

Would you accept such a syntax if I were to work on it?

@waylan
Copy link
Member

waylan commented Dec 20, 2014

@SoftwareMaven thanks for your detailed proposal. However I am not interested. Of course, you are free to implement a third party extension which implements whatever syntax you would like. If your extensions become widely popular, then perhaps I might reconsider.

That said, I think you have a decent start on a proposal. With the assumption that you will be pursuing a third party extension, here is my feedback:

Given you example:

* List item
* Another list item
{: ^target=list .list-class } {: .list-item-class }

Block level attribute lists are always at the end of the block, so shouldn't the ul attribute list be after the li attribute list? Even if your extension became wildly popular, I would insist on that change before merging back into the built-in extension. And if that is the case, I think it seems reasonable to restrict them to only occur at the end of the block. My suggestion would be to only support parent attribute lists at the end.

Regarding the actual syntax, I think use of the ^ is clever, although I'm not crazy about it being used for multiple things (it is already used for footnotes). Of course, Markdown reuses the same characters all over its syntax, so that's just a personal preference of mine.

What I don't like is the "target=some-thing-that-is-only-implied-in-the-document" part. Maybe something simpler like {^ .list-class} (where ^ means the immediate parent--I see no reason to support any additional parents higher up the chain). Although that is non-obvious when reading the document later. That said, I find the "target=some-thing-that-is-only-implied-in-the-document" syntax to be non-obvious also.

Another possibility would be to use CSS style selectors (see some interesting suggestions) at the li level to apply to the parent (??? {: .list-item-class :parent.list-class :parent#parent-id} ???). Although haven't typed it and looked at it, I don't care for it any more that your proposal.

Either way, I don't like it as applies to a construct that does not exist in the Markdown document. Sorry, I just can't get past that point.

@Goddard
Copy link

Goddard commented Nov 17, 2019

just add a default css tag. Pretty simple. class=markdown-table

problem solved.

@prahladyeri
Copy link

just add a default css tag. Pretty simple. class=markdown-table

problem solved.

Can you please elaborate on how and where can I add a default class to generated HTML elements? I couldn't find any such property or attribute in the pelicanconf.py, nor in the theme.

@richard-jones
Copy link

For anyone that's looking for a quick and dirty solution to this, I have implemented an extension for myself which mirrors the Kramdown syntax (because I am having to render markdown that's been written previously for Jekyll).

Syntax is:

for lists

{:.tabular-list}
- list item
- list item

for tables

{:.my-table}
| header | header |
| --- | --- |
| etc

Caveat: I wrote this in about an hour, as my first ever attempt at a markdown extension. It works for what I needed it for, but I have not tested it properly by any means. YMMV

import markdown
import re
from markdown.extensions import attr_list


def makeExtension(**kwargs):
    return ImpliedAttrListExtension(**kwargs)


class ImpliedAttrListExtension(markdown.Extension):
    """Extension for attatching `attr_list` entries to implied elements.  Specifically: lists and tables"""

    def extendMarkdown(self, md: markdown.Markdown, *args, **kwargs):
        md.preprocessors.register(ImpliedAttrListPreprocessor(md), "implied_attr_list", 100)
        md.treeprocessors.register(ImpliedAttrListTreeprocessor(md), 'implied_attr_list', 100)
        md.registerExtension(self)


class ImpliedAttrListPreprocessor(markdown.preprocessors.Preprocessor):

    def run(self, lines):
        """
        Insert a blank line in between the declaration of the attr_list and the thing that it applies to
        This will allow it to render the list normally.  The attr_list will get rendered into the text of a paragraph
        tag which the Treeprocessor below will handle
        """

        new_lines = []
        for line in lines:
            new_lines.append(line)
            if re.fullmatch(ImpliedAttrListTreeprocessor.BASE_RE, line):
                new_lines.append("")
        return new_lines


class ImpliedAttrListTreeprocessor(attr_list.AttrListTreeprocessor):

    def run(self, doc):
        """
        Iterate through the doc, locating <p> tags that contain ONLY the syntax for attr_lists.

        Once one is found, the value is applied to the next element in the iteration of the doc, and the
        <p> tag is removed

        :param doc:
        :return:
        """
        holdover = None
        removes = []
        for elem in doc.iter():
            if holdover is not None:
                self.assign_attrs(elem, holdover)
                holdover = None

            if elem.tag in ["p"] and elem.text is not None:
                m = re.fullmatch(self.BASE_RE, elem.text)
                if m:
                    holdover = m.group(1)
                    removes.append(elem)

        if len(removes) > 0:
            parent_map = {c: p for p in doc.iter() for c in p}
            for r in removes:
                parent = parent_map[r]
                parent.remove(r)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants