Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

Questo file documenta gli avanzamenti significativi e le decisioni chiave del progetto `normattiva_2_md`.

## 2026-06-05

### Feature: --no-notes flag (issue #24)

- Aggiunto `--no-notes` alla CLI: esclude i blocchi `AGGIORNAMENTO (N)` dall'output Markdown
- Aggiunto `no_notes=False` a `convert_url()`, `convert_xml()` e funzioni interne dell'API
- Il parametro percorre tutta la catena: `generate_markdown_text` → `process_content_with_paragraphs`
- I marcatori `(( ))` da `<ins>/<del>` rimangono invariati (non sono note storiche)
- Aggiunti 6 test in `tests/test_no_notes.py`

## 2026-01-30

### Release v2.1.10: Progressive OpenData Fallback Retry
Expand Down
52 changes: 52 additions & 0 deletions openspec/changes/add-no-notes-flag/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Proposal: add-no-notes-flag

## Summary

Aggiunge il flag `--no-notes` alla CLI e il parametro `no_notes` all'API pubblica per escludere dall'output Markdown i blocchi di note `AGGIORNAMENTO` presenti nei documenti Akoma Ntoso.

## Motivazione

Issue #24: chi usa normattiva2md con sistemi RAG (Retrieval-Augmented Generation) segnala che i blocchi `AGGIORNAMENTO (N)` — testo descrittivo delle modifiche storiche, non testo normativo vigente — creano rumore nel retrieval. Serve un modo per ottenere solo il testo della norma corrente.

## Scope

Due capability coinvolte:

| Capability | Tipo di modifica |
|---|---|
| `cli-interface` | MODIFIED — aggiunge `--no-notes` |
| `markdown-conversion` | MODIFIED — aggiunge `no_notes` nelle funzioni di conversione |

## Comportamento atteso

Con `--no-notes`:
- I blocchi `AGGIORNAMENTO (N)` e il testo che li segue all'interno dello stesso elemento `<content>` vengono omessi dall'output.
- I marcatori `(( testo modificato ))` da `<ins>/<del>` **rimangono**: fanno parte del testo vigente, non delle note storiche.

Senza `--no-notes` (default): comportamento invariato — nessuna regressione.

## Architettura della modifica

Il parametro `no_notes` percorre la catena di chiamate esistente:

```
CLI (--no-notes)
→ api.py: convert_url(no_notes=...) / convert_xml(no_notes=...)
→ _convert_xml_internal(no_notes=...)
→ generate_markdown_text(no_notes=...)
→ generate_markdown_fragments(no_notes=...)
→ extract_body_fragments(no_notes=...)
→ process_content_with_paragraphs(no_notes=...)
```

Punto di filtraggio: in `process_content_with_paragraphs()` (markdown_converter.py:222), quando `no_notes=True`, i paragrafi che iniziano con `"AGGIORNAMENTO"` vengono saltati.

## Fuori scope

- Rimozione dei marcatori `(( ))` (potrebbero essere una feature separata futura).
- `convert_akomantoso_to_markdown_improved()` nella CLI legacy — da aggiornare solo se ancora usata direttamente.

## Impatto stimato

- Basso: ~30 righe di codice, nessuna dipendenza esterna.
- Test: aggiungere uno scenario nel test suite esistente con un file XML che contiene blocchi AGGIORNAMENTO.
33 changes: 33 additions & 0 deletions openspec/changes/add-no-notes-flag/specs/cli-interface/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# cli-interface — Delta Spec

## MODIFIED Requirements

### Requirement: Notes Filter Parameter
The system SHALL provide a `--no-notes` flag to exclude AGGIORNAMENTO annotation blocks from the Markdown output.

#### Scenario: Basic usage with local XML file
- **WHEN** user runs `normattiva2md --no-notes input.xml output.md`
- **THEN** output SHALL not contain any paragraph starting with `AGGIORNAMENTO`
- **AND** all other article text SHALL be preserved unchanged
- **AND** inline modifications `(( ... ))` SHALL remain in the output

#### Scenario: Usage with normattiva.it URL
- **WHEN** user runs `normattiva2md --no-notes "https://www.normattiva.it/..." output.md`
- **THEN** conversion SHALL download the XML normally
- **AND** output SHALL exclude AGGIORNAMENTO blocks
- **AND** exit code SHALL be 0

#### Scenario: Default behavior unchanged
- **WHEN** user runs `normattiva2md input.xml output.md` without `--no-notes`
- **THEN** AGGIORNAMENTO blocks SHALL appear in the output as before
- **AND** no behavioral change from current version

#### Scenario: Combination with --art
- **WHEN** user runs `normattiva2md --no-notes --art 4 input.xml`
- **THEN** output SHALL contain only article 4
- **AND** AGGIORNAMENTO blocks within article 4 SHALL be excluded

#### Scenario: Help documentation
- **WHEN** user runs `normattiva2md --help`
- **THEN** `--no-notes` SHALL be listed in the options table
- **AND** description SHALL explain that it excludes AGGIORNAMENTO annotation blocks
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# markdown-conversion — Delta Spec

## MODIFIED Requirements

### Requirement: Notes Filter in Conversion Functions
The conversion API SHALL accept a `no_notes` parameter that, when `True`, omits AGGIORNAMENTO annotation blocks from the generated Markdown.

#### Scenario: convert_url with no_notes=True
- **WHEN** `convert_url(url, no_notes=True)` is called
- **THEN** returned `ConversionResult.markdown` SHALL not contain paragraphs starting with `AGGIORNAMENTO`
- **AND** inline `(( ... ))` modifications SHALL be preserved
- **AND** all metadata in `ConversionResult` SHALL be unchanged

#### Scenario: convert_xml with no_notes=True
- **WHEN** `convert_xml(xml_path, no_notes=True)` is called
- **THEN** returned `ConversionResult.markdown` SHALL not contain AGGIORNAMENTO blocks
- **AND** conversion SHALL complete without errors

#### Scenario: Default value preserves backward compatibility
- **WHEN** `convert_url(url)` or `convert_xml(xml_path)` is called without `no_notes`
- **THEN** behavior SHALL be identical to current version
- **AND** no AGGIORNAMENTO content SHALL be removed

#### Scenario: Parameter propagation through internal functions
- **WHEN** `no_notes=True` is passed to `convert_url` or `convert_xml`
- **THEN** `no_notes` SHALL be forwarded to `_convert_xml_internal`
- **AND** from there to `generate_markdown_text`
- **AND** from there to `generate_markdown_fragments` and `extract_body_fragments`
- **AND** ultimately to `process_content_with_paragraphs` where filtering occurs

#### Scenario: Filtering scope limited to AGGIORNAMENTO paragraphs
- **WHEN** `no_notes=True` and an article contains both normal paragraphs and AGGIORNAMENTO blocks
- **THEN** only paragraphs whose text starts with `AGGIORNAMENTO` SHALL be omitted
- **AND** all other paragraph text SHALL appear in the output unchanged
Comment on lines +31 to +34
- **AND** `<ins>/<del>` wrapped text (rendered as `(( ... ))`) SHALL not be affected
20 changes: 20 additions & 0 deletions openspec/changes/add-no-notes-flag/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Tasks: add-no-notes-flag

## Implementation

- [ ] **1. markdown_converter.py** — aggiunge `no_notes=False` a `process_content_with_paragraphs()`: salta i paragrafi che iniziano con `"AGGIORNAMENTO"` quando `no_notes=True`
- [ ] **2. markdown_converter.py** — propaga `no_notes` attraverso la catena: `generate_markdown_text` → `generate_markdown_fragments` → `extract_body_fragments` → `process_body_element` → `process_article` → `process_content_with_paragraphs`
- [ ] **3. api.py** — aggiunge `no_notes=False` a `convert_url()`, `convert_xml()`, `_convert_xml_internal()`; passa il parametro a `generate_markdown_text()`
- [ ] **4. cli.py** — aggiunge `--no-notes` ad argparse; passa `no_notes` alla funzione di conversione; aggiorna la tabella help Rich

## Validation

- [ ] **5. Test** — aggiunge test in `tests/` con file XML contenente blocchi AGGIORNAMENTO: verifica che con `no_notes=True` siano assenti nell'output e con `no_notes=False` siano presenti
- [ ] **6. Smoke test CLI** — `normattiva2md --no-notes test_data/20050516_005G0104_VIGENZA_20250130.xml` non deve contenere "AGGIORNAMENTO" nell'output

## Notes

- Task 1 e 2 sono dipendenti (fare in sequenza).
- Task 3 dipende da 1+2.
- Task 4 dipende da 3.
- Task 5 e 6 parallelizzabili dopo task 4.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "normattiva2md"
version = "2.1.10"
version = "2.1.11"
description = "Convertitore da XML Akoma Ntoso a formato Markdown con download automatico delle leggi citate e cross-references inline (CLI: normattiva2md)"
readme = "README.md"
requires-python = ">=3.7"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def read_readme():

setup(
name="normattiva2md",
version="2.1.10",
version="2.1.11",
description="Convertitore da XML Akoma Ntoso a formato Markdown con download automatico delle leggi citate e cross-references inline (CLI: normattiva2md)",
long_description=read_readme(),
long_description_content_type="text/markdown",
Expand Down
6 changes: 6 additions & 0 deletions src/normattiva2md/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def convert_url(
with_urls: bool = False,
force_opendata: bool = False,
quiet: bool = False,
no_notes: bool = False,
) -> Optional[ConversionResult]:
Comment on lines 58 to 62
"""
Converte documento da URL normattiva.it a Markdown.
Expand Down Expand Up @@ -160,6 +161,7 @@ def convert_url(
with_urls=with_urls,
metadata=metadata,
quiet=quiet,
no_notes=no_notes,
)

if result:
Expand All @@ -182,6 +184,7 @@ def convert_xml(
with_urls: bool = False,
metadata: Optional[Dict] = None,
quiet: bool = False,
no_notes: bool = False,
) -> Optional[ConversionResult]:
Comment on lines 184 to 188
"""
Converte file XML locale a Markdown.
Expand Down Expand Up @@ -225,6 +228,7 @@ def convert_xml(
with_urls=with_urls,
metadata=metadata,
quiet=quiet,
no_notes=no_notes,
)


Expand All @@ -234,6 +238,7 @@ def _convert_xml_internal(
with_urls: bool = False,
metadata: Optional[Dict] = None,
quiet: bool = False,
no_notes: bool = False,
) -> Optional[ConversionResult]:
"""
Internal conversion function used by both convert_url and convert_xml.
Expand Down Expand Up @@ -284,6 +289,7 @@ def _convert_xml_internal(
ns=AKN_NAMESPACE,
metadata=metadata,
cross_references=cross_references,
no_notes=no_notes,
)

if not quiet:
Expand Down
8 changes: 8 additions & 0 deletions src/normattiva2md/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def print_rich_help():
"--opendata", "Forza download Akoma Ntoso via API OpenData (ZIP AKN)"
)
proc_table.add_row("--with-references", "Scarica anche leggi citate")
proc_table.add_row("--no-notes", "Esclude blocchi AGGIORNAMENTO dall'output")
proc_table.add_row("--provvedimenti", "Esporta provvedimenti attuativi CSV")
proc_table.add_row("--validate", "Validazione strutturale del Markdown")
console.print(proc_table)
Expand Down Expand Up @@ -410,6 +411,11 @@ def main():
action="store_true",
help="Genera link Markdown agli URL originali di normattiva.it per gli articoli citati",
)
parser.add_argument(
"--no-notes",
action="store_true",
help="Esclude i blocchi AGGIORNAMENTO (note storiche delle modifiche) dall'output Markdown",
)
parser.add_argument(
"--opendata",
action="store_true",
Expand Down Expand Up @@ -753,6 +759,7 @@ def main():
metadata=metadata,
article_ref=article_ref,
with_urls=args.with_urls,
no_notes=args.no_notes,
)

if success:
Expand Down Expand Up @@ -817,6 +824,7 @@ def main():
metadata=None,
article_ref=article_filter_eid,
with_urls=args.with_urls,
no_notes=args.no_notes,
)

if success:
Expand Down
Loading