We'll process sitemap.xml files to:
- Update lastmod dates
- Remove deprecated URLs
- Add new pages
- Standardize formatting
usage: python3 -m alterx.xml [-h] [--depth-first] [--follow-symlinks] [--exclude GLOB] [--include GLOB] [--sizes min..max]
[--depth min..max] [--paths-from FILE] [--pretty | --no-pretty] [--ns-clean] [--recover] [--strip-ws]
[--strip-comments] [--strip-pi] [--xml-declaration | --no-xml-declaration] [-m] [-d NAME=VALUE] [-x SCRIPT]
[-o FILE] [--encoding USE_ENCODING] [-n]
[PATH ...]
positional arguments:
PATH
options:
-h, --help show this help message and exit
--pretty, --no-pretty
Save pretty formated
--ns-clean Try to clean up redundant namespace declarations
--recover Try hard to parse through broken XML
--strip-ws Discard blank text nodes between tags
--strip-comments Discard comments
--strip-pi Discard processing instructions
--xml-declaration, --no-xml-declaration
Add xml declaration
-m Modify flag
-d NAME=VALUE Define some variable
-x SCRIPT Extension script
-o FILE Output to FILE
--encoding USE_ENCODING
Encoding to use when saving
-n No modifiaction will happend
Traversal:
--depth-first Process each directory's contents before the directory itself
--follow-symlinks Follow symbolic links
--exclude GLOB exclude matching GLOB
--include GLOB include matching GLOB
--sizes min..max Filter sizes: 1k.., 4g, ..2mb
--depth min..max Check for depth: 2.., 4, ..3
--paths-from FILE read list of source-file names from FILEmkdir -p websites
cat > websites/sitemap_old.xml <<EOF
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/home</loc>
<lastmod>2022-01-01</lastmod>
</url>
<url>
<loc>https://example.com/old-page</loc>
<lastmod>2021-05-15</lastmod>
</url>
</urlset>
EOF
cat > websites/sitemap_new.xml <<EOF
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/blog</loc>
</url>
</urlset>
EOFfrom datetime import datetime
def init(app):
# Configuration
app.defs.update({
'DEPRECATED_PATHS': ['/old-page', '/temp'],
'NEW_URLS': [
{'loc': 'https://example.com/contact', 'priority': '0.8'},
{'loc': 'https://example.com/about'}
],
'DEFAULT_LASTMOD': datetime.now().strftime('%Y-%m-%d')
})
def process(doc, file_info, app):
ns = {'sm': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
root = doc.getroot()
# Remove deprecated URLs
for url in root.findall('sm:url', ns):
loc = url.find('sm:loc', ns)
if any(path in loc.text for path in app.defs['DEPRECATED_PATHS']):
root.remove(url)
# Update lastmod dates
for url in root.findall('sm:url', ns):
lastmod = url.find('sm:lastmod', ns)
if lastmod is None:
lastmod = app.etree.SubElement(url, 'lastmod')
lastmod.text = app.defs['DEFAULT_LASTMOD']
elif lastmod.text < app.defs['DEFAULT_LASTMOD']:
lastmod.text = app.defs['DEFAULT_LASTMOD']
# Add new URLs
existing_urls = {url.find('sm:loc', ns).text for url in root.findall('sm:url', ns)}
for new_url in app.defs['NEW_URLS']:
if new_url['loc'] not in existing_urls:
url_elem = app.etree.SubElement(root, 'url')
app.etree.SubElement(url_elem, 'loc').text = new_url['loc']
app.etree.SubElement(url_elem, 'lastmod').text = app.defs['DEFAULT_LASTMOD']
if 'priority' in new_url:
app.etree.SubElement(url_elem, 'priority').text = new_url['priority']
def end(app):
print(f"Processed {app.total.Files} sitemaps")
print(f"Removed {getattr(app.total, 'Removed', 0)} deprecated URLs")
print(f"Added {getattr(app.total, 'Added', 0)} new URLs")# Using the `-mm` flag, write only if modified
# With pretty printing and XML declaration
python -m alterx.xml -mm --pretty --xml-declaration -x sitemap_updater.py websites
# Alternative with lxml for better formatting
python -m alterx.xml -mm --pretty --ns-clean -x sitemap_updater.py websitessitemap_old.xml:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/home</loc>
<lastmod>2023-11-15</lastmod>
</url>
<url>
<loc>https://example.com/contact</loc>
<lastmod>2023-11-15</lastmod>
<priority>0.8</priority>
</url>
<url>
<loc>https://example.com/about</loc>
<lastmod>2023-11-15</lastmod>
</url>
</urlset>sitemap_new.xml:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/blog</loc>
<lastmod>2023-11-15</lastmod>
</url>
<url>
<loc>https://example.com/contact</loc>
<lastmod>2023-11-15</lastmod>
<priority>0.8</priority>
</url>
<url>
<loc>https://example.com/about</loc>
<lastmod>2023-11-15</lastmod>
</url>
</urlset>import unittest
import tempfile
import xml.etree.ElementTree as ET
from pathlib import Path
from alterx.xml import AlterXML
from datetime import datetime
class TestSitemapProcessing(unittest.TestCase):
def setUp(self):
self.test_dir = Path(tempfile.mkdtemp())
# Create test sitemap
self.sitemap = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://test.com/old</loc>
<lastmod>2020-01-01</lastmod>
</url>
<url>
<loc>https://test.com/current</loc>
</url>
</urlset>"""
(self.test_dir / "sitemap.xml").write_text(self.sitemap)
# Create processor script
self.script = self.test_dir / "sitemap_updater.py"
self.script.write_text("""
from datetime import datetime
def init(app):
app.defs.update({
'DEPRECATED_PATHS': ['/old'],
'NEW_URLS': [{'loc': 'https://test.com/new'}],
'TODAY': datetime.now().strftime('%Y-%m-%d')
})
def process(doc, stat, app):
modified = False
root = doc.getroot()
ns = {'sm': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
# Track changes for testing
stat.added = 0
stat.removed = 0
# Remove old URLs
for url in root.findall('sm:url', ns):
loc = url.find('sm:loc', ns)
if any(path in loc.text for path in app.defs['DEPRECATED_PATHS']):
root.remove(url)
stat.removed += 1
modified = True
# Add lastmod if missing
for url in root.findall('sm:url', ns):
if url.find('sm:lastmod', ns) is None:
ET.SubElement(url, 'lastmod').text = app.defs['TODAY']
modified = True
# Add new URLs
existing = {u.find('sm:loc', ns).text for u in root.findall('sm:url', ns)}
for new in app.defs['NEW_URLS']:
if new['loc'] not in existing:
url = ET.SubElement(root, 'url')
ET.SubElement(url, 'loc').text = new['loc']
ET.SubElement(url, 'lastmod').text = app.defs['TODAY']
stat.added += 1
def end(app):
app.total.Added = sum(hasattr(s, 'added') and s.added for s in app._stats)
app.total.Removed = sum(hasattr(s, 'removed') and s.removed for s in app._stats)
""")
def tearDown(self):
import shutil
shutil.rmtree(self.test_dir)
def test_sitemap_processing(self):
# Run processor
app = AlterXML()
app.main([
"-m",
"--pretty",
"-x", str(self.script),
str(self.test_dir / "sitemap.xml")
])
# Verify output
tree = ET.parse(self.test_dir / "sitemap.xml")
root = tree.getroot()
ns = {'sm': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
# Check URLs
urls = [url.find('sm:loc', ns).text for url in root.findall('sm:url', ns)]
self.assertNotIn('https://test.com/old', urls)
self.assertIn('https://test.com/current', urls)
self.assertIn('https://test.com/new', urls)
# Check lastmod dates
today = datetime.now().strftime('%Y-%m-%d')
for url in root.findall('sm:url', ns):
lastmod = url.find('sm:lastmod', ns)
self.assertIsNotNone(lastmod)
self.assertEqual(lastmod.text, today)
# Verify stats
self.assertEqual(app.total.Added, 1)
self.assertEqual(app.total.Removed, 1)
if __name__ == "__main__":
unittest.main()- Namespace Handling: Proper XML namespace management
- Date Processing: Automatic lastmod date updates
- Element Manipulation: Adding, removing and modifying nodes
- Statistics Tracking: Custom counters for added/removed URLs
- XML Declaration: Preserving/adding XML declarations
- Pretty Printing: Formatted output for readability
This example shows how to use alterx for professional XML processing tasks, with specific optimizations for sitemap files. The same pattern can be adapted for other XML formats like RSS feeds, configuration files, or data interchange formats.