# How Free-TV/IPTV Parses Markdown Table Columns Separated by Pipe Characters

> Learn how the Free-TV/IPTV script parses markdown table columns. Discover how it splits pipe-delimited data and extracts channel information efficiently.

- Repository: [Free TV/IPTV](https://github.com/Free-TV/IPTV)
- Tags: how-to-guide
- Published: 2026-06-17

---

**The [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py) script strips whitespace from each table row, splits the string on the pipe character (`|`), and extracts specific indices to populate `Channel` object attributes.**

The Free-TV/IPTV repository converts Markdown-based channel listings into M3U playlists using a Python script that interprets pipe-delimited tables. Understanding how this script parses markdown table columns separated by pipe characters reveals the data pipeline that transforms static documentation into streaming playlists. The parsing logic resides primarily in the `Channel` class constructor within [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py).

## How the Pipe-Delimited Parsing Works

### Stripping and Splitting Lines

The script begins by normalizing each line of input to remove extraneous whitespace. According to the source code in [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py) (lines 98-103), the parser executes:

```python
md_line = md_line.strip()
parts = md_line.split("|")

```

This operation creates a list where each element represents a column value. Because standard Markdown tables begin with a leading pipe character, the resulting list (`parts`) always contains an empty string at index 0.

### Handling the Leading Empty Element

When processing a typical table row such as `| 101 | BBC News | [link](http://stream.example.com) |`, the split operation produces:

```python
['', ' 101 ', ' BBC News ', ' [link](http://stream.example.com) ', '']

```

The script ignores the empty boundaries and accesses meaningful data starting at index 1.

## Extracting Column Data from Split Arrays

The `Channel` constructor maps specific `parts` indices to semantic attributes as implemented in lines 101-106 of [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py):

- **Channel number**: `parts[1]` → `self.number = parts[1].strip()`
- **Channel name**: `parts[2]` → `self.name = parts[2].strip()`
- **Stream URL**: `parts[3]` → Extracted from Markdown link syntax
- **Logo URL**: `parts[4]` → Extracted from HTML `img` tag
- **Optional EPG ID**: `parts[5]` (if present) → `self.epg = parts[5].strip()`

Consider this example Markdown row:

```markdown
| 101 | BBC News | [link](http://stream.example.com/bbc.m3u8) | <img src="http://logo.example.com/bbc.png"> |

```

The script processes it as follows:

```python
md_line = "| 101 | BBC News | [link](http://stream.example.com/bbc.m3u8) | <img src=\"http://logo.example.com/bbc.png\"> |"
parts = md_line.strip().split("|")

# Result: ['', ' 101 ', ' BBC News ', ' [link](http://stream.example.com/bbc.m3u8) ', ' <img src="http://logo.example.com/bbc.png"> ', '']

number = parts[1].strip()  # "101"

name = parts[2].strip()    # "BBC News"

```

## Parsing Embedded URLs and HTML

### Extracting Stream URLs from Markdown Links

The URL column in the Markdown file uses standard link syntax: `[text](http://example.com)`. The script extracts the actual URL by locating the parentheses boundaries:

```python
self.url = parts[3].strip()
self.url = self.url[self.url.find("(")+1:self.url.rfind(")")]

```

This slices the string from the character after the opening `(` to the character before the closing `)`, yielding the raw stream address.

### Extracting Logo URLs from Image Tags

Similarly, the logo column contains an HTML `<img>` tag. The parser locates the `src="..."` attribute to isolate the image URL:

```python
self.logo = parts[4].strip()
self.logo = self.logo[self.logo.find('src="')+5:self.logo.rfind('"')]

```

The `+5` offset accounts for the length of `src="` itself, positioning the slice at the start of the actual URL.

## Complete Channel Construction Flow

The entire parsing sequence executes within the `Channel` constructor, which is invoked for every line containing the channel entry marker (`"[>]"`) while iterating over the Markdown files in `lists/*.md`. The constructor signature and parsing logic ensure that raw table rows transform into structured objects with clean URLs ready for M3U playlist generation.

## Summary

- The script uses `strip()` and `split("|")` to tokenize pipe-separated table columns in [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py).
- The resulting list contains an empty element at index 0 due to the leading pipe, so data extraction begins at index 1.
- **Channel numbers** and **names** are extracted directly from indices 1 and 2 after stripping whitespace.
- **Stream URLs** require parsing Markdown link syntax by extracting text between `(` and `)` characters.
- **Logo URLs** require parsing HTML `img` tags by extracting the `src` attribute value between quotes.
- Optional **EPG IDs** occupy index 5 when present in the table row.

## Frequently Asked Questions

### How does the script handle empty columns in the Markdown table?

The script relies on the pipe delimiter to maintain positional alignment. If a column is empty, the split operation creates an empty string at that index (e.g., `['', ' 101 ', '', ' ...']`), which subsequent `.strip()` calls process as an empty value. The constructor assumes standard table structure, so missing data at expected indices may result in empty string assignments to the `Channel` attributes.

### Why does the split result contain an empty string at the beginning?

Markdown table syntax begins with a leading pipe character (`|`) before the first cell content. When Python's `split("|")` encounters this leading delimiter, it creates an empty string as the first element of the resulting list. The script accounts for this by accessing data starting at index 1 rather than index 0.

### Where does the parsing logic reside in the repository?

The core parsing logic is defined in [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py) at lines 98-106, specifically within the `Channel` class constructor. This file reads the Markdown tables from `lists/*.md`, processes the pipe-separated columns, and generates the final M3U playlist files containing the extracted stream URLs.

### How does the script differentiate between header rows and data rows?

The script identifies valid channel entries by checking for the `[>]` marker within the line before invoking the `Channel` constructor. This marker indicates a playable channel entry rather than a table header, separator, or empty row, ensuring only relevant data undergoes the pipe-splitting extraction process.