Skip to content

Advanced Project 4 — Document Conversion Pipeline

Difficulty: Advanced · Estimated effort: 2 weeks · Prerequisites: Module 11 — RAII and Smart Pointers, Module 13 — Exception Handling, Module 15 — SOLID and Design Patterns · Demanded pattern decisions: chain of responsibility, abstract factory

This is one of the designated integration projects, and one of the projects that requires a design-pattern decision: at least two patterns must be named and justified by a requirement, and one alternative must be considered and declined in writing.

No complete solution is provided. The stage model is the design decision, and it is not made for you here.


1. Scenario

A department converts documents between formats for an archive: plain text with markup, a simplified Markdown-like format, and a comma-separated tabular format. The conversion is not one step but several — normalise whitespace, expand includes, apply a template, reflow to a width, then serialise — and different archives want different stage sets.

The current tool is a single function containing five transformations and every combination of flags, and adding a stage means editing the flags, the help text and the tests.


2. Problem statement

Build a conversion pipeline: a document model in the middle, readers that produce it, stages that transform it, writers that serialise it, and a configuration that assembles a pipeline from a file. A stage that cannot handle a document must pass it on rather than fail, and a stage that genuinely cannot proceed must fail with a message that names the stage.


3. User requirements

  • [ ] Convert a file from any supported input format to any supported output format.
  • [ ] Choose the stages in a configuration file, including none.
  • [ ] See each stage's effect: a --trace mode printing the document between stages.
  • [ ] Get a failure message naming the stage, the input file and the line, where applicable.
  • [ ] Add a new stage or format without editing the pipeline runner.
  • [ ] Convert a directory of files in one run, reporting per-file results.

4. Functional requirements

  • [ ] A Document model: sections, paragraphs, inline emphasis, and tables if you support them.
  • [ ] A Reader abstraction with at least two implementations, chosen by file extension or configuration.
  • [ ] A Stage abstraction with at least four implementations, including one that can decline a document entirely.
  • [ ] A Writer abstraction with at least two implementations.
  • [ ] A Pipeline owning ordered stages, running a document through them.
  • [ ] A configuration loader assembling readers, stages and writers by name.
  • [ ] Typed errors: unknown format or stage, malformed input with a line number, and a stage failure naming the stage.

5. Non-functional requirements

  • [ ] The pipeline runner contains no knowledge of any specific stage, reader or writer.
  • [ ] A failing stage is reported with its name and leaves no partial output file behind.
  • [ ] Stages are independent: reordering them in the configuration never crashes, even if the result is odd.
  • [ ] Ownership is explicit; documents are moved between stages rather than copied, or the copy cost is measured and reported.
  • [ ] Builds warning-free with g++ -std=c++17 -Wall -Wextra -Werror.

6. Suggested features

  • A stage that collects statistics about the document and passes it through unchanged.
  • Templates with placeholders filled from a small key/value file.
  • A --dry-run mode listing the stages that would run, in order.
  • A second output format for the trace, so traces can be diffed between runs.

7. OOP concepts involved

Concept Required Where it appears
Multiple classes yes Document model, readers, stages, writers, pipeline, configuration, error types
Relationships yes Pipeline ◆ stages; stage ──> document; writer ◆ output stream; reader ┈┈> document factory
Encapsulation yes The document model's invariants hold after every stage; no stage pokes at another's internals
Inheritance or composition yes Interfaces for the three extension points; the pipeline composes stages and owns none of them
Polymorphism yes Readers, stages and writers all dispatch; the runner names none of them
The STL yes std::vector for stage order, std::map for registries, <sstream> for parsing and formatting
Exception handling yes Unknown names, malformed input and stage failures are distinct types with context

8. Suggested design considerations

The pattern decision you must make and justify

Chain of responsibility, for the stages. A stage usually transforms and passes on, but a stage that cannot handle the document must pass it through rather than crash — that is the chain. The alternative is a strategy: every stage must handle every document, returning the document unchanged when it has nothing to do. Both work; you must argue which requirement settled it for you.

Abstract factory, for readers and writers. Format selection must produce a family of related objects (reader, writer, and possibly a format-specific stage set) without the configuration loader naming concrete classes. The alternative is a registry of std::function factories — lighter, and arguably enough. Choose, and decline the other with a reason: that is the declined-pattern item.

Ideas worth weighing and recording: decorator, if a stage is really a wrapper around an output (worth considering and probably not earned); memento, if you want stage-level undo (not earned unless you add a preview mode).

Other design points

The document model is the contract. Stages compose only because they agree on one model. If a stage needs private access to another's data, the model is missing a concept — not the stage missing a friend declaration.

Partial output is a real hazard. If stage three of five fails after writing a line, the archive has a corrupt file. Write to a temporary and rename on success; test the failure path.

Trace mode pays for itself twice. It makes the pipeline observable during development, and it makes the tests readable: input, trace, output, all as text.


9. Minimum viable version

One reader, two stages (normalise whitespace, reflow), one writer, a pipeline that runs them in order, and a small main converting one file. Errors named, but no configuration file.


10. Intermediate extension

Add the third format, the configuration file, and two more stages including one that declines a document. Then verify that a pipeline with the declining stage in the middle runs to completion and that a genuinely failing stage leaves no output file.


11. Advanced extension

Add directory conversion with per-file reporting, the trace mode and the statistics stage. Then write the design note: the two patterns, the declined alternative, and the decision you would reverse — with the cost. Include a real trace as evidence.


12. Testing checklist

  • [ ] Each reader produces the documented document model from a small sample
  • [ ] Each writer produces byte-identical output for the same document, run twice
  • [ ] A round trip through a lossless pair of formats preserves the document
  • [ ] A stage that declines a document passes it through unchanged (compared byte-for-byte)
  • [ ] A stage that fails reports its own name and the input line
  • [ ] A failing stage leaves no partial output file on disk
  • [ ] An unknown stage or format in the configuration is refused with the name in the message
  • [ ] Reordering the stages never crashes, for every permutation of four stages
  • [ ] A pipeline with zero stages reproduces the input document at the output
  • [ ] Converting a directory reports one line per file, and continues past a bad file
  • [ ] Trace output between stages matches the expected document at each step

13. Documentation checklist

  • [ ] The document model, drawn as a small class diagram
  • [ ] The three extension points and how to add to each, in a short "how to extend" section
  • [ ] The configuration format with a full example
  • [ ] The two patterns, the requirement each serves, and the declined alternative
  • [ ] A real trace, pasted, showing the document between two stages

14. Self-evaluation rubric

Criterion Excellent looks like Weight
Extension points New reader, stage or writer needs no change to the runner 30%
Pattern decisions Two patterns justified; the declined alternative argued with a requirement 20%
Model quality Stages compose through one model with no back doors 15%
Failure handling Named-stage failures, no partial output, per-file reporting 15%
Testability Trace mode plus byte-level round-trip tests 10%
Documentation Model, configuration and extension guide written down 10%

Related: Advanced projects · Project roadmap · All projects · Module 15 — SOLID and Design Patterns