# lxml User Guide — PythonSTB Android App
Generated by RIMI
## What is lxml?
lxml is the most feature-rich and performance-optimized library for processing XML
and HTML in Python. It wraps the C libraries libxml2 and libxslt, providing a
Pythonic API while maintaining near-native speed.
---
## Why We Need It
PythonSTB uses lxml for:
1. **M3U playlist parsing** — Parse HLS/DASH playlists (XML-based)
2. **EPG (Electronic Program Guide)** — Parse XMLTV format EPG data
3. **HTML scraping** — Extract data from web pages (stream URLs, titles)
4. **SOAP/XML-RPC communication** — Some IPTV APIs use XML responses
5. **XSLT transforms** — Convert between XML formats
---
## Installed Packages
| Package | Version | Description |
|---------|---------|-------------|
| lxml | 6.1.1 | Core XML/HTML processing |
| lxml_html_clean | 0.4.5 | HTML sanitizer (bundled) |
---
## Basic Usage
### Parsing XML
```python
from lxml import etree
# Parse from string
xml_string = '''
Channel 1
http://example.com/stream.m3u8
http://example.com/logo.png
'''
root = etree.fromstring(xml_string.encode())
print(root.find('name').text) # Channel 1
print(root.find('url').text) # http://example.com/stream.m3u8
# Parse from file
tree = etree.parse('playlist.xml')
root = tree.getroot()
```
### Parsing HTML
```python
from lxml import html
# Parse HTML string
page = html.fromstring('''
''')
# XPath to find elements
channels = page.xpath('//div[@class="stream"]')
for ch in channels:
name = ch.xpath('.//h2/text()')[0]
url = ch.xpath('.//a/@href')[0]
print(f"{name}: {url}")
```
### M3U Playlist Parsing
```python
from lxml import etree
def parse_m3u(content):
"""Parse M3U/M3U8 playlist into list of channels."""
channels = []
lines = content.strip().split('\n')
i = 0
while i < len(lines):
line = lines[i].strip()
if line.startswith('#EXTINF:'):
# Parse info line
info = line[8:] # Remove #EXTINF:
attrs = {}
# Extract duration
if ',' in info:
duration, name = info.rsplit(',', 1)
attrs['duration'] = duration
attrs['name'] = name.strip()
# Next line should be the URL
if i + 1 < len(lines):
url = lines[i + 1].strip()
if not url.startswith('#'):
attrs['url'] = url
channels.append(attrs)
i += 2
continue
i += 1
return channels
# Usage
with open('playlist.m3u', 'r', encoding='utf-8') as f:
content = f.read()
channels = parse_m3u(content)
for ch in channels:
print(f"{ch.get('name', 'Unknown')}: {ch.get('url', 'N/A')}")
```
### EPG (XMLTV) Parsing
```python
from lxml import etree
from datetime import datetime
def parse_epg(xml_content):
"""Parse XMLTV format EPG data."""
root = etree.fromstring(xml_content.encode())
channels = {}
for channel in root.findall('.//channel'):
ch_id = channel.get('id')
display_name = channel.find('display-name').text
icon = channel.find('icon')
icon_url = icon.get('src') if icon is not None else None
channels[ch_id] = {
'name': display_name,
'icon': icon_url
}
programmes = []
for prog in root.findall('.//programme'):
programmes.append({
'channel': prog.get('channel'),
'start': prog.get('start'),
'stop': prog.get('stop'),
'title': prog.find('title').text if prog.find('title') is not None else '',
'desc': prog.find('desc').text if prog.find('desc') is not None else ''
})
return channels, programmes
```
### XPath Examples
```python
from lxml import etree
tree = etree.parse('data.xml')
root = tree.getroot()
# Find all elements with attribute
elements = root.xpath('//item[@type="channel"]')
# Find with text content
items = root.xpath('//item[name="Channel 1"]')
# Find with contains
links = root.xpath('//a[contains(@href, "m3u8")]')
# Find with multiple conditions
results = root.xpath('//channel[@lang="en" and @country="US"]')
# Find parent element
child = root.find('.//child')
parent = child.getparent()
# Find siblings
next_sibling = child.getnext()
prev_sibling = child.getprevious()
```
---
## Features Used in App
| Feature | Module | Usage |
|---------|--------|-------|
| XML parsing | etree | EPG, API responses |
| HTML parsing | html | Web scraping |
| XPath | etree | Data extraction |
| ElementTree API | etree | Tree manipulation |
| HTML cleaning | lxml_html_clean | Sanitize scraped HTML |
| XSLT | etree | Format conversion |
| C14N | etree | Canonical XML output |
---
## Troubleshooting
### Import Error
```python
# If lxml fails to import:
try:
from lxml import etree
print("lxml is installed correctly")
except ImportError as e:
print(f"lxml import error: {e}")
# Check if .so files exist and have correct permissions
```
### Encoding Issues
```python
# Always specify encoding when parsing
tree = etree.parse('file.xml') # Auto-detect
tree = etree.parse(open('file.xml', 'rb')) # Binary mode
# Force encoding
content = open('file.xml', 'r', encoding='latin-1').read()
root = etree.fromstring(content.encode('utf-8'))
```
### Memory Errors
```python
# Use iterparse for large files
import xml.etree.ElementTree as ET # fallback for huge files
# Or use lxml's iterparse
context = etree.iterparse('large.xml', events=('end',), tag='item')
for event, elem in context:
process(elem)
elem.clear() # Free memory
```
---
## Related Packages
| Package | Purpose |
|---------|---------|
| lxml_html_clean | HTML sanitization |
| cssselect | CSS selector support |
| cssutils | CSS parsing |
| beautifulsoup4 | Alternative HTML parser |
---
## Android-Specific Notes
- lxml is compiled with `-Wl,-z,norelro` for Android compatibility
- Uses 16KB page alignment for Android 15+ support
- Links statically against libxml2 and libxslt (no external .so files)
- All `.so` files include `librimi.so` dependency
Generated by RIMI
---
## App Integration
```python
# Example: Full M3U parser with EPG support
from lxml import etree
from lxml.html import fromstring
class IPTVParser:
def __init__(self):
self.channels = []
def parse_m3u(self, url):
"""Fetch and parse M3U playlist."""
import urllib.request
with urllib.request.urlopen(url) as response:
content = response.read().decode('utf-8')
return self._parse_m3u_content(content)
def _parse_m3u_content(self, content):
"""Parse M3U content string."""
channels = []
lines = content.strip().split('\n')
for i, line in enumerate(lines):
if line.startswith('#EXTINF:'):
# Parse attributes
attrs = {}
if ',' in line:
attrs['name'] = line.split(',')[-1].strip()
# Get URL (next non-comment line)
url = lines[i + 1].strip() if i + 1 < len(lines) else ''
if url and not url.startswith('#'):
attrs['url'] = url
channels.append(attrs)
return channels
def parse_epg(self, xml_content):
"""Parse XMLTV EPG data."""
root = etree.fromstring(xml_content.encode())
return {
'channels': self._parse_epg_channels(root),
'programmes': self._parse_epg_programmes(root)
}
def _parse_epg_channels(self, root):
channels = {}
for ch in root.findall('.//channel'):
channels[ch.get('id')] = ch.find('display-name').text
return channels
def _parse_epg_programmes(self, root):
programmes = []
for prog in root.findall('.//programme'):
programmes.append({
'channel': prog.get('channel'),
'title': prog.find('title').text,
'start': prog.get('start')
})
return programmes
```