# Microformats for Rust

This repository provides a parser implementation as a library and binary for
the Microformats standard.

## Features

### Core Microformats Parsing
- Complete microformats2 parsing support
- JSON output format compatible with microformats.org standards
- Support for all standard microformat types (h-card, h-entry, h-event, etc.)

### Experimental Extensions

#### 📷 Picture Element Parsing (Experimental)
Enhanced support for parsing HTML `<picture>` elements with responsive image capabilities.

**Enable with feature flag:**
```toml
[dependencies]
microformats = { version = "0.18", features = ["picture"] }
```

**Features:**
- Multiple `<source>` elements with srcset and media query support
- Automatic fallback image handling
- Support for `u-photo`, `u-logo`, `u-avatar`, and `p-name` properties
- URL resolution relative to document base URL

**Example:**
```html
<picture class="u-photo">
    <source srcset="/images/hero-800w.jpg 800w, /images/hero-400w.jpg 400w"
            media="(min-width: 800px)" type="image/jpeg">
    <source srcset="/images/hero-600h.jpg 600h"
            media="(min-width: 600px)" type="image/webp">
    <img src="/images/fallback.jpg" alt="Hero image">
</picture>
```

⚠️ **Experimental**: This feature is subject to change as it evolves.

#### 🧹 Document Cleaning (Experimental)
Filter documents to contain only recognized microformat classes.

**Enable with feature flag:**
```toml
[dependencies]
microformats = { version = "0.18", features = ["cleaner"] }
```

**Usage:**
```rust
use microformats::extensions::CleanedDocument;

let doc = microformats::from_html(html, &url)?;
let cleaned = doc.with_only_known_classes();
```

### Using Experimental Extensions Together

For comprehensive usage examples and advanced patterns when using both experimental extensions together, see the [Experimental Extensions Integration Guide](#experimental-extensions-integration-guide).

## Experimental Extensions Integration Guide

### Features Overview

The microformats-rust library includes two experimental extensions that can be used together:

1. **Cleaner Feature** - Filters documents to contain only recognized microformat classes
2. **Picture Feature** - Enhanced parsing of HTML `<picture>` elements with responsive image support

### Enabling Both Features

Add to your `Cargo.toml`:

```toml
[dependencies]
microformats = { version = "0.18", features = ["extensions", "cleaner", "picture"] }
```

Or enable individual extensions:

```toml
[dependencies]
microformats = { version = "0.18", features = ["cleaner", "picture"] }
```

### Basic Usage Example

```rust
use microformats::{from_html, extensions::CleanedDocument};
use url::Url;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let html = r#"
    <article class="h-entry">
        <h1 class="p-name">My Blog Post</h1>
        
        <picture class="u-photo">
            <source srcset="/images/hero-800w.jpg 800w, /images/hero-400w.jpg 400w"
                    media="(min-width: 800px)" type="image/jpeg">
            <img src="/images/fallback.jpg" alt="Hero image">
        </picture>
        
        <div class="e-content">
            <p>This is a blog post with responsive images.</p>
        </div>
    </article>
    "#;

    let base_url: Url = "https://example.com/".parse()?;
    
    // Parse the HTML with picture support enabled
    let document = from_html(html, &base_url)?;
    println!("Original document has {} items", document.items.len());
    
    // Clean the document to only include recognized classes
    let cleaned_document = document.with_only_known_classes();
    println!("Cleaned document has {} items", cleaned_document.items.len());

    Ok(())
}
```

### Working with Picture Elements

The picture feature enhances parsing of responsive image elements:

```rust
use microformats::{from_html, extensions::CleanedDocument};
use microformats_types::PropertyValue;
use url::Url;

let html = r#"
<article class="h-entry">
    <h1 class="p-name">Gallery Post</h1>
    
    <!-- Multiple picture elements -->
    <picture class="u-photo">
        <source srcset="gallery/hero-1200w.jpg 1200w, gallery/hero-800w.jpg 800w, gallery/hero-400w.jpg 400w"
                media="(min-width: 800px)" type="image/jpeg">
        <source srcset="gallery/hero.webp" media="(min-width: 768px)" type="image/webp">
        <img src="gallery/hero-fallback.jpg" alt="Hero gallery image">
    </picture>
    
    <picture class="u-photo">
        <source srcset="gallery/thumb-400w.jpg 400w" type="image/jpeg">
        <img src="gallery/thumb.jpg" alt="Thumbnail">
    </picture>
</article>
"#;

let base_url: Url = "https://gallery.example.com/".parse()?;
let document = from_html(html, &base_url)?;
let cleaned_document = document.with_only_known_classes();

// Access the parsed images
if let Some(item) = cleaned_document.items.first() {
    if let Some(photos) = item.properties.get("photo") {
        for (i, photo) in photos.iter().enumerate() {
            match photo {
                PropertyValue::Image(image) => {
                    println!("Photo {}: {} (alt: {:?})", i + 1, image.value, image.alt);
                }
                PropertyValue::Url(url) => {
                    println!("Photo {}: {} (no alt text)", i + 1, url.value());
                }
                _ => {}
            }
        }
    }
}
```

### Filtering Custom Classes with Pictures

The cleaner feature removes unrecognized classes while preserving picture data:

```rust
use microformats::{from_html, extensions::CleanedDocument};
use url::Url;

let html = r#"
<!-- Valid microformat content -->
<article class="h-entry">
    <h1 class="p-name">Valid Content</h1>
    <picture class="u-photo">
        <img src="/valid.jpg" alt="Valid picture">
    </picture>
</article>

<!-- Custom content that will be filtered -->
<div class="h-custom-post">
    <h2 class="p-custom-title">Custom Post</h2>
    <picture class="u-custom-photo">
        <img src="/custom.jpg" alt="Custom picture">
    </picture>
</div>

<!-- Mixed valid and custom -->
<article class="h-entry h-custom-mixed">
    <h1 class="p-name">Mixed Article</h1>
    <picture class="u-photo">
        <img src="/mixed-valid.jpg" alt="Valid picture in mixed article">
    </picture>
</article>
"#;

let base_url: Url = "https://example.com/".parse()?;
let document = from_html(html, &base_url)?;

// Check what items are unrecognized
let unrecognized = document.unrecognized_items();
println!("Found {} unrecognized items", unrecognized.len());

// Clean the document
let cleaned_document = document.with_only_known_classes();
println!("After cleaning: {} items remain", cleaned_document.items.len());

// Only the pure h-entry will remain (the mixed one gets filtered too)
```

### Nested Structures with Pictures

Complex nested structures work with both features:

```rust
use microformats::{from_html, extensions::CleanedDocument};
use url::Url;

let html = r#"
<div class="h-feed">
    <h1 class="p-name">Photo Feed</h1>
    
    <article class="h-entry">
        <h2 class="p-name">Entry 1</h2>
        <picture class="u-photo">
            <source srcset="/entry1-800w.jpg 800w" type="image/jpeg">
            <img src="/entry1.jpg" alt="First entry">
        </picture>
        
        <div class="p-author h-card">
            <picture class="u-photo">
                <source srcset="/authors/author1-2x.jpg 2x" type="image/jpeg">
                <img src="/authors/author1.jpg" alt="Author 1">
            </picture>
            <span class="p-name">John Doe</span>
        </div>
    </article>
    
    <!-- Valid entry with custom child (child will be filtered) -->
    <article class="h-entry">
        <h2 class="p-name">Entry 2</h2>
        <picture class="u-photo">
            <img src="/entry2.jpg" alt="Second entry">
        </picture>
        
        <div class="p-location h-custom-location">
            <picture class="u-photo">
                <img src="/locations/custom.jpg" alt="Custom location">
            </picture>
            <span class="p-name">Custom Location</span>
        </div>
    </article>
    
    <!-- Completely custom entry (will be filtered) -->
    <div class="h-custom-entry">
        <picture class="u-photo">
            <img src="/custom-entry.jpg" alt="Custom entry">
        </picture>
    </div>
</div>
"#;

let base_url: Url = "https://feed.example.com/".parse()?;
let document = from_html(html, &base_url)?;
let cleaned_document = document.with_only_known_classes();

// The cleaned document will preserve:
// - The h-feed
// - Both h-entry items  
// - Valid nested h-card (author)
// But will filter:
// - The h-custom-location
// - The h-custom-entry
```

### Real-World Example: Travel Blog

Here's a complete example showing how both extensions work together in a realistic scenario:

```rust
use microformats::{from_html, extensions::CleanedDocument};
use microformats_types::PropertyValue;
use url::Url;

let travel_blog_html = r#"
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="author me" href="/author">
</head>
<body>
    <article class="h-entry" id="japan-trip">
        <header class="p-author h-card">
            <picture class="u-photo">
                <source srcset="/avatars/jane-2x.jpg 2x, /avatars/jane-1x.jpg 1x" type="image/jpeg">
                <img src="/avatars/jane.jpg" alt="Jane Traveler">
            </picture>
            <a href="/jane" class="p-name u-url">Jane Traveler</a>
        </header>
        
        <h1 class="p-name">Amazing Trip to Japan</h1>
        
        <!-- Hero image with multiple responsive sources -->
        <picture class="u-photo">
            <source srcset="/japan/hero-1200w.jpg 1200w, /japan/hero-800w.jpg 800w, /japan/hero-400w.jpg 400w"
                    media="(min-width: 1200px)" type="image/jpeg">
            <source srcset="/japan/hero-tablet.webp" media="(min-width: 768px)" type="image/webp">
            <source srcset="/japan/hero-mobile.jpg" media="(min-width: 320px)" type="image/jpeg">
            <img src="/japan/hero-fallback.jpg" alt="Cherry blossoms in Tokyo">
        </picture>
        
        <div class="e-content">
            <p>I recently visited Japan and it was absolutely amazing!</p>
            
            <!-- Inline gallery images -->
            <picture class="u-photo">
                <source srcset="/japan/temple-800w.jpg 800w" type="image/jpeg">
                <img src="/japan/temple.jpg" alt="Ancient temple in Kyoto">
            </picture>
            
            <div class="p-location h-card">
                <picture class="u-photo">
                    <source srcset="/locations/kyoto-400w.jpg 400w" type="image/jpeg">
                    <img src="/locations/kyoto.jpg" alt="Kyoto city">
                </picture>
                <span class="p-name">Kyoto, Japan</span>
            </div>
        </div>
        
        <!-- Custom section (will be filtered by cleaner) -->
        <aside class="h-custom-sidebar">
            <picture class="u-photo">
                <img src="/custom/sidebar.jpg" alt="Custom sidebar content">
            </picture>
        </aside>
    </article>
</body>
</html>
"#;

let base_url: Url = "https://travel.example.com/".parse()?;
let document = from_html(travel_blog_html, &base_url)?;

// See what would be filtered out
let unrecognized = document.unrecognized_items();
println!("Custom items to filter: {}", unrecognized.len());

// Clean the document
let cleaned_document = document.with_only_known_classes();

// Work with the cleaned document
if let Some(entry) = cleaned_document.items.first() {
    println!("Entry: {:?}", entry.properties.get("name"));
    
    // Extract all photos from picture elements
    if let Some(photos) = entry.properties.get("photo") {
        println!("Found {} photos:", photos.len());
        for (i, photo) in photos.iter().enumerate() {
            if let PropertyValue::Image(image) = photo {
                println!("  {}: {} ({})", i + 1, image.value, image.alt.as_ref().unwrap_or(&"no alt".into()));
            }
        }
    }
    
    // Extract author information with their avatar
    if let Some(PropertyValue::Item(author)) = entry.properties.get("author").unwrap().first() {
        println!("Author: {:?}", author.properties.get("name"));
        if let Some(author_photos) = author.properties.get("photo") {
            println!("Author avatar: {:?}", author_photos.first());
        }
    }
}
```

### Error Handling

Both features provide error handling for edge cases:

```rust
use microformats::{from_html, extensions::CleanedDocument};
use url::Url;

let problematic_html = r#"
<div class="h-entry">
    <h1 class="p-name">Problematic Post</h1>
    
    <!-- Picture with malformed srcset -->
    <picture class="u-photo">
        <source srcset="invalid-url 400w,, /valid.jpg 800w" type="image/jpeg">
        <img src="/fallback.jpg"> <!-- No alt text -->
    </picture>
    
    <!-- Empty picture -->
    <picture class="u-photo">
    </picture>
</div>

<div class="h-invalid-class">
    <picture class="u-photo">
        <img src="/invalid.jpg" alt="Should be filtered">
    </picture>
</div>
"#;

let base_url: Url = "https://example.com/".parse()?;

// Parsing should succeed despite malformed HTML
let document = from_html(problematic_html, &base_url)?;
println!("Parsed {} items total", document.items.len());

// Cleaning should filter out invalid classes while preserving valid ones
let cleaned = document.with_only_known_classes();
println!("Cleaned to {} valid items", cleaned.items.len());

// Check what was filtered
let unrecognized = document.unrecognized_items();
println!("Filtered {} unrecognized items", unrecognized.len());
```

### Performance Considerations

The extensions are designed to work efficiently together:

```rust
use microformats::{from_html, extensions::CleanedDocument};
use std::time::Instant;
use url::Url;

// Generate content with many picture elements
let mut html = String::from(r#"<div class="h-feed"><h1 class="p-name">Large Feed</h1>"#);
for i in 0..100 {
    html.push_str(&format!(r#"
    <article class="h-entry">
        <h2 class="p-name">Entry {}</h2>
        <picture class="u-photo">
            <source srcset="/images/{}-800w.jpg 800w, /images/{}-400w.jpg 400w" type="image/jpeg">
            <img src="/images/{}-fallback.jpg" alt="Entry {}">
        </picture>
    </article>
    "#, i, i, i, i, i));
}
html.push_str("</div>");

let base_url: Url = "https://large.example.com/".parse()?;

let start = Instant::now();
let document = from_html(&html, &base_url)?;
let cleaned = document.with_only_known_classes();
let duration = start.elapsed();

println!("Processed 100 entries with pictures in {:?}", duration);
println!("Result: {} items after cleaning", cleaned.items.len());
```

### Best Practices

1. **Enable Both Features Together**: Use both `cleaner` and `picture` features when you need responsive image support with class filtering.

2. **Check Unrecognized Items**: Use `unrecognized_items()` before cleaning to see what will be filtered.

3. **Handle Picture Properties Gracefully**: Always check for both `Image` and `Url` property values since pictures without alt text may return URLs instead.

4. **Preserve Metadata**: Cleaning preserves document metadata like URL and language, so you don't lose contextual information.

5. **Performance**: The extensions are optimized for performance, but consider the impact when processing very large numbers of picture elements.

### Migration Guide

If you're already using one feature and want to add the other:

**Adding Picture Feature to Existing Cleaner Usage:**
```rust
// Before (cleaner only)
let document = from_html(html, &base_url)?;
let cleaned = document.with_only_known_classes();

// After (both features)
// No changes needed - picture parsing is automatic when feature is enabled
```

**Adding Cleaner Feature to Existing Picture Usage:**
```rust
// Before (picture only)
let document = from_html(html, &base_url)?;

// After (both features)
let document = from_html(html, &base_url)?;
let cleaned = document.with_only_known_classes(); // Add this line
```

Both extensions work seamlessly together, requiring minimal changes to existing code.

## Recent Changes

### microformats2-parsing issue #50 - Rel Attributes Collection
- **Updated**: `LinkRelationExpander` now collects ALL `rel` attributes from each HTML element
- **Enhanced**: Multiple `rel` attributes on the same element are now properly combined and parsed
- **Improved**: Better handling of elements with multiple `rel` attributes for comprehensive microformats parsing
- **Tests Added**: Comprehensive test suite covering multiple rel attributes, edge cases, and same-element scenarios

This change ensures that all `rel` attributes are processed, providing more complete and accurate microformats parsing.

### 🆕 Experimental Picture Parsing Feature
- **Added**: Enhanced parsing of HTML `<picture>` elements
- **Features**: Support for multiple image sources, srcset attributes, and media queries
- **Properties**: Works with `u-photo`, `u-logo`, `u-avatar`, and `p-name` microformat properties
- **Note**: Experimental feature, may change in future versions


