Goal: Replace string content with proper model classes in Choice/Fallback Duration: 6-8 hours (compressed timeline) Expected Outcome: 274/274 tests passing (100%)
AlternateContent infrastructure works perfectly, but Choice and Fallback currently store nested content as :string attributes. This violates the model-driven architecture principle.
Current (WRONG):
class Choice
attribute :content, :string # ❌ XML as string
end
class Fallback
attribute :content, :string # ❌ XML as string
endTarget (CORRECT):
class Choice
# Multiple possible nested elements (polymorphic)
attribute :drawing, Drawing
attribute :pict, Pict
attribute :paragraph, Paragraph
attribute :table, Table
# ... etc
end
class Fallback
# Multiple possible nested elements (polymorphic)
attribute :pict, Pict
attribute :shape, Shape
# ... etc
endFrom test failures, AlternateContent contains these structures:
<mc:Choice Requires="wps">
<w:drawing>
<wp:anchor>
<wp:extent cx="8280000" cy="4953480"/>
<a:graphic>
<a:graphicData uri="...wps...">
<wps:wsp> <!-- Word Processing Shape -->
<wps:txbx> <!-- Text Box -->
<w:txbxContent>
<!-- Paragraphs, SDTs, etc. -->
</w:txbxContent>
</wps:txbx>
</wps:wsp>
</a:graphicData>
</a:graphic>
</wp:anchor>
</w:drawing>
</mc:Choice>Models Needed:
- ✅ Drawing (exists)
- ✅ Anchor (exists)
⚠️ Anchor needs children: extent, graphic, sizeRelH, sizeRelV- ❌ WordProcessingShape (wps:wsp)
- ❌ TextBox (wps:txbx)
- ❌ TextBoxContent (w:txbxContent)
<mc:Fallback>
<w:pict>
<v:shape type="#_x0000_t202" style="...">
<v:textbox style="...">
<w:txbxContent>
<!-- Paragraphs, SDTs, etc. -->
</w:txbxContent>
</v:textbox>
</v:shape>
</w:pict>
</mc:Fallback>Models Needed:
- ❌ Pict (w:pict)
⚠️ Shape (v:shape) - exists but needs enhancement⚠️ VML Textbox (v:textbox) - needs creation- ❌ TextBoxContent (w:txbxContent) - shared with DrawingML
Priority 1: Shared TextBoxContent
# lib/uniword/wordprocessingml/text_box_content.rb
class TextBoxContent < Lutaml::Model::Serializable
attribute :paragraphs, Paragraph, collection: true
attribute :tables, Table, collection: true
attribute :sdts, StructuredDocumentTag, collection: true
xml do
root 'txbxContent'
namespace Uniword::Ooxml::Namespaces::WordProcessingML
mixed_content
map_element 'p', to: :paragraphs, render_nil: false
map_element 'tbl', to: :tables, render_nil: false
map_element 'sdt', to: :sdts, render_nil: false
end
endPriority 2: Pict (VML Picture container)
# lib/uniword/wordprocessingml/pict.rb
class Pict < Lutaml::Model::Serializable
attribute :shapes, Vml::Shape, collection: true
xml do
root 'pict'
namespace Uniword::Ooxml::Namespaces::WordProcessingML
mixed_content
map_element 'shape', to: :shapes,
namespace: Uniword::Ooxml::Namespaces::VML,
render_nil: false
end
endPriority 3: VML Textbox
# lib/uniword/vml/textbox.rb
class Textbox < Lutaml::Model::Serializable
attribute :style, :string
attribute :inset, :string
attribute :content, Uniword::Wordprocessingml::TextBoxContent
xml do
root 'textbox'
namespace Uniword::Ooxml::Namespaces::VML
mixed_content
map_attribute 'style', to: :style, render_nil: false
map_attribute 'inset', to: :inset, render_nil: false
map_element 'txbxContent', to: :content,
namespace: Uniword::Ooxml::Namespaces::WordProcessingML,
render_nil: false
end
endPriority 4: Enhance VML Shape
# lib/uniword/vml/shape.rb (enhance existing)
class Shape < Lutaml::Model::Serializable
# Existing attributes...
attribute :textbox, Textbox
attribute :wrap, Wrap
xml do
# Existing mappings...
map_element 'textbox', to: :textbox, render_nil: false
map_element 'wrap', to: :wrap, render_nil: false
end
endPriority 5: Word Processing Shape
# lib/uniword/drawingml/word_processing_shape.rb
class WordProcessingShape < Lutaml::Model::Serializable
attribute :non_visual_properties, NonVisualDrawingProperties
attribute :shape_properties, ShapeProperties
attribute :text_box, TextBox
attribute :body_properties, BodyProperties
xml do
root 'wsp'
namespace Uniword::Ooxml::Namespaces::WordProcessingShape
mixed_content
map_element 'cNvSpPr', to: :non_visual_properties, render_nil: false
map_element 'spPr', to: :shape_properties, render_nil: false
map_element 'txbx', to: :text_box, render_nil: false
map_element 'bodyPr', to: :body_properties, render_nil: false
end
endPriority 6: DrawingML TextBox (wps:txbx)
# lib/uniword/drawingml/text_box.rb
class TextBox < Lutaml::Model::Serializable
attribute :content, Uniword::Wordprocessingml::TextBoxContent
xml do
root 'txbx'
namespace Uniword::Ooxml::Namespaces::WordProcessingShape
mixed_content
map_element 'txbxContent', to: :content,
namespace: Uniword::Ooxml::Namespaces::WordProcessingML,
render_nil: false
end
endPriority 7: Enhance Anchor
# lib/uniword/wordprocessingml/anchor.rb (enhance existing)
class Anchor < Lutaml::Model::Serializable
# Existing attributes...
attribute :extent, Extent
attribute :graphic, DrawingML::Graphic
attribute :size_rel_h, SizeRelH
attribute :size_rel_v, SizeRelV
xml do
# Existing mappings...
map_element 'extent', to: :extent,
namespace: Uniword::Ooxml::Namespaces::WordProcessingDrawing,
render_nil: false
# ... other mappings
end
endPriority 8: WordProcessingShape Namespace
# lib/uniword/ooxml/namespaces.rb
class WordProcessingShape < Lutaml::Model::XmlNamespace
uri 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape'
prefix_default 'wps'
element_form_default :qualified
endPriority 9: Update Choice/Fallback
# lib/uniword/wordprocessingml/choice.rb
class Choice < Lutaml::Model::Serializable
attribute :requires, McRequires
# Polymorphic nested content (multiple possible)
attribute :drawing, Drawing
attribute :paragraph, Paragraph
attribute :table, Table
xml do
root 'Choice'
namespace Uniword::Ooxml::Namespaces::MarkupCompatibility
mixed_content
map_attribute 'Requires', to: :requires
map_element 'drawing', to: :drawing, render_nil: false
map_element 'p', to: :paragraph, render_nil: false
map_element 'tbl', to: :table, render_nil: false
end
end
# lib/uniword/wordprocessingml/fallback.rb
class Fallback < Lutaml::Model::Serializable
# Polymorphic nested content
attribute :pict, Pict
attribute :paragraph, Paragraph
xml do
root 'Fallback'
namespace Uniword::Ooxml::Namespaces::MarkupCompatibility
mixed_content
map_element 'pict', to: :pict, render_nil: false
map_element 'p', to: :paragraph, render_nil: false
end
endPriority 10: Update Tests
Update spec/uniword/wordprocessingml/alternate_content_spec.rb to use proper models instead of string content.
Priority 11: Run Tests
# Unit tests
bundle exec rspec spec/uniword/wordprocessingml/alternate_content_spec.rb
# Baseline (must stay green)
bundle exec rspec spec/uniword/styleset_roundtrip_spec.rb \
spec/uniword/theme_roundtrip_spec.rb
# Document elements (target: 16/16)
bundle exec rspec spec/uniword/document_elements_roundtrip_spec.rb
# Total (target: 274/274)
bundle exec rspec spec/uniword/Expected Results:
- AlternateContent: 12/12 ✅
- Baseline: 342/342 ✅
- Document Elements: 16/16 ✅ (up from 8/16)
- Total: 274/274 (100%) ✅
lib/uniword/wordprocessingml/text_box_content.rblib/uniword/wordprocessingml/pict.rblib/uniword/vml/textbox.rblib/uniword/vml/wrap.rblib/uniword/drawingml/word_processing_shape.rblib/uniword/drawingml/text_box.rblib/uniword/drawingml/extent.rblib/uniword/drawingml/size_rel_h.rblib/uniword/drawingml/size_rel_v.rblib/uniword/drawingml/body_properties.rb
lib/uniword/ooxml/namespaces.rb(add WordProcessingShape namespace)lib/uniword/wordprocessingml/choice.rb(replace :content with models)lib/uniword/wordprocessingml/fallback.rb(replace :content with models)lib/uniword/vml/shape.rb(add textbox, wrap)lib/uniword/wordprocessingml/anchor.rb(add extent, graphic, size)lib/uniword/wordprocessingml/drawing.rb(verify integration)spec/uniword/wordprocessingml/alternate_content_spec.rb(update tests)
- All new classes follow Pattern 0 (attributes BEFORE xml)
- MECE architecture maintained (clear separation)
- Model-driven (ZERO :string attributes for XML content)
- Polymorphic content handling (multiple possible nested elements)
- Zero baseline regressions (342/342 maintained)
- All document element tests passing (16/16)
- Total: 274/274 tests passing (100%)
##Key Principles
- Model-Driven: Every XML element is a proper class, NO string storage
- Polymorphic: Choice/Fallback can contain multiple types of nested content
- Reusability: TextBoxContent shared between VML and DrawingML
- Namespaces: Proper namespace handling for cross-namespace elements
- Pattern 0: ALWAYS attributes before xml blocks
Session 2A: Foundation (2-3h) → Priorities 1-4 complete
Session 2B: DrawingML (2-3h) → Priorities 5-8 complete
Session 2C: Integration (1-2h) → Priorities 9-10 complete
Session 2D: Verification (1h) → Priority 11, 274/274 achieved ✅
Total: 6-9 hours (compressed from typical 12-15h)
- Read this plan thoroughly
- Start with Session 2A (Foundation Classes)
- Test after each class creation
- Maintain zero baseline regressions
- Achieve 274/274 (100%) target
See: PHASE5_SESSION2_PROMPT.md for detailed start instructions