@@ -686,3 +686,196 @@ def test_xmp_information_namespace_prefix():
686
686
assert xmp ._get_namespace_prefix (pypdf .xmp .PDFAID_NAMESPACE ) == "pdfaid"
687
687
assert xmp ._get_namespace_prefix (pypdf .xmp .PDFX_NAMESPACE ) == "pdfx"
688
688
assert xmp ._get_namespace_prefix ("unknown://namespace" ) == "unknown"
689
+
690
+
691
+ def test_xmp_information_owner_document_none_errors ():
692
+ """Test error handling when ownerDocument is None."""
693
+ xmp = XmpInformation .create ()
694
+
695
+ # Save original owner document
696
+ original_owner = xmp .rdf_root .ownerDocument
697
+
698
+ try :
699
+ # Remove existing descriptions to force creation of new one
700
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
701
+ xmp .rdf_root .removeChild (desc )
702
+
703
+ # Set ownerDocument to None to trigger error conditions
704
+ xmp .rdf_root .ownerDocument = None
705
+
706
+ # Test _get_or_create_description error (lines 459-465)
707
+ with pytest .raises (RuntimeError , match = "XMP Document is None" ):
708
+ xmp ._get_or_create_description ()
709
+
710
+ # Test _update_stream error (line 597)
711
+ with pytest .raises (RuntimeError , match = "XMP Document is None" ):
712
+ xmp ._update_stream ()
713
+
714
+ # Restore owner document for other tests (but clear the descriptions again)
715
+ xmp .rdf_root .ownerDocument = original_owner
716
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
717
+ xmp .rdf_root .removeChild (desc )
718
+ xmp .rdf_root .ownerDocument = None
719
+
720
+ # Test _set_single_value error (line 484) - this will try to create description
721
+ with pytest .raises (RuntimeError , match = "XMP Document is None" ):
722
+ xmp .set_dc_coverage ("test coverage" )
723
+
724
+ # Restore and clear again for bag values test
725
+ xmp .rdf_root .ownerDocument = original_owner
726
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
727
+ xmp .rdf_root .removeChild (desc )
728
+ xmp .rdf_root .ownerDocument = None
729
+
730
+ # Test _set_bag_values error (line 506)
731
+ with pytest .raises (RuntimeError , match = "XMP Document is None" ):
732
+ xmp .set_dc_contributor (["contributor" ])
733
+
734
+ # Restore and clear again for seq values test
735
+ xmp .rdf_root .ownerDocument = original_owner
736
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
737
+ xmp .rdf_root .removeChild (desc )
738
+ xmp .rdf_root .ownerDocument = None
739
+
740
+ # Test _set_seq_values error (line 535)
741
+ with pytest .raises (RuntimeError , match = "XMP Document is None" ):
742
+ xmp .set_dc_creator (["creator" ])
743
+
744
+ # Restore and clear again for langalt values test
745
+ xmp .rdf_root .ownerDocument = original_owner
746
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
747
+ xmp .rdf_root .removeChild (desc )
748
+ xmp .rdf_root .ownerDocument = None
749
+
750
+ # Test _set_langalt_values error (line 564)
751
+ with pytest .raises (RuntimeError , match = "XMP Document is None" ):
752
+ xmp .set_dc_title ({"x-default" : "title" })
753
+
754
+ finally :
755
+ # Restore original owner document
756
+ xmp .rdf_root .ownerDocument = original_owner
757
+
758
+
759
+ def test_xmp_information_remove_existing_attribute ():
760
+ """Test removing existing attribute node (line 479)."""
761
+ xmp = XmpInformation .create ()
762
+
763
+ # Set a single value first to create an attribute
764
+ xmp .set_dc_coverage ("initial coverage" )
765
+ assert xmp .dc_coverage == "initial coverage"
766
+
767
+ # Set a different value to trigger attribute removal and replacement
768
+ xmp .set_dc_coverage ("updated coverage" )
769
+ assert xmp .dc_coverage == "updated coverage"
770
+
771
+ # Set to None to remove the attribute entirely
772
+ xmp .set_dc_coverage (None )
773
+ assert xmp .dc_coverage is None
774
+
775
+
776
+ def test_xmp_information_edge_case_coverage ():
777
+ """Test additional edge cases for complete coverage."""
778
+ xmp = XmpInformation .create ()
779
+
780
+ # Test setting empty values
781
+ xmp .set_dc_contributor ([])
782
+ assert xmp .dc_contributor == []
783
+
784
+ xmp .set_dc_creator ([])
785
+ assert xmp .dc_creator == []
786
+
787
+ xmp .set_dc_title ({})
788
+ assert xmp .dc_title == {}
789
+
790
+ # Test setting None values
791
+ xmp .set_dc_contributor (None )
792
+ assert xmp .dc_contributor == []
793
+
794
+ xmp .set_dc_creator (None )
795
+ assert xmp .dc_creator == []
796
+
797
+ xmp .set_dc_title (None )
798
+ assert xmp .dc_title == {}
799
+
800
+
801
+ def test_xmp_information_create_new_description ():
802
+ """Test creating new description elements (lines 462-465)."""
803
+ xmp = XmpInformation .create ()
804
+
805
+ # Remove all existing descriptions
806
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
807
+ xmp .rdf_root .removeChild (desc )
808
+
809
+ # Create a new description with specific about URI (covers lines 462-465)
810
+ desc = xmp ._get_or_create_description ("test-uri" )
811
+ assert desc .getAttributeNS (pypdf .xmp .RDF_NAMESPACE , "about" ) == "test-uri"
812
+
813
+ # Test that it creates the element with proper namespace
814
+ assert desc .tagName == "rdf:Description"
815
+ assert desc .namespaceURI == pypdf .xmp .RDF_NAMESPACE
816
+
817
+
818
+ def test_xmp_information_attribute_handling ():
819
+ """Test attribute node removal and creation (line 479, 484, 506, 535, 564)."""
820
+ xmp = XmpInformation .create ()
821
+
822
+ # Remove all existing descriptions first
823
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
824
+ xmp .rdf_root .removeChild (desc )
825
+
826
+ # Test _set_single_value with new description creation (covers line 484 path where doc is not None)
827
+ xmp .set_dc_coverage ("test coverage" )
828
+ assert xmp .dc_coverage == "test coverage"
829
+
830
+ # Test _set_bag_values with new description creation (covers line 506 path where doc is not None)
831
+ xmp .set_dc_contributor (["contributor1" , "contributor2" ])
832
+ assert xmp .dc_contributor == ["contributor1" , "contributor2" ]
833
+
834
+ # Test _set_seq_values with new description creation (covers line 535 path where doc is not None)
835
+ xmp .set_dc_creator (["creator1" , "creator2" ])
836
+ assert xmp .dc_creator == ["creator1" , "creator2" ]
837
+
838
+ # Test _set_langalt_values with new description creation (covers line 564 path where doc is not None)
839
+ xmp .set_dc_title ({"x-default" : "Test Title" , "en" : "Test Title EN" })
840
+ assert xmp .dc_title == {"x-default" : "Test Title" , "en" : "Test Title EN" }
841
+
842
+ # Test attribute node removal (line 479) by setting an attribute first, then changing it
843
+ xmp .set_dc_format ("application/pdf" )
844
+ assert xmp .dc_format == "application/pdf"
845
+
846
+ # Change the value - this should trigger attribute node removal
847
+ xmp .set_dc_format ("text/plain" )
848
+ assert xmp .dc_format == "text/plain"
849
+
850
+
851
+ def test_xmp_information_complete_coverage ():
852
+ """Test remaining uncovered lines for complete coverage."""
853
+ xmp = XmpInformation .create ()
854
+
855
+ # Test scenario where ownerDocument is available for all setter error paths
856
+ # First remove all descriptions to force new creation
857
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
858
+ xmp .rdf_root .removeChild (desc )
859
+
860
+ # Test scenario where ownerDocument is available
861
+
862
+ # Test the case where ownerDocument is not None in _set_single_value (covers line 484 success path)
863
+ desc = xmp ._get_or_create_description ()
864
+ desc .setAttribute ("test" , "value" )
865
+ # Now modify an existing attribute to test attribute removal (line 479)
866
+ xmp .set_dc_source ("original" )
867
+ xmp .set_dc_source ("modified" ) # This should trigger existing attribute removal
868
+ assert xmp .dc_source == "modified"
869
+
870
+ # Force recreate and test non-None document paths in other setters
871
+ for desc in list (xmp .rdf_root .getElementsByTagNameNS (pypdf .xmp .RDF_NAMESPACE , "Description" )):
872
+ xmp .rdf_root .removeChild (desc )
873
+
874
+ # Test success paths (non-None document) for all setter types
875
+ xmp .set_dc_contributor (["test1" ]) # covers line 506 success path
876
+ xmp .set_dc_creator (["test2" ]) # covers line 535 success path
877
+ xmp .set_dc_title ({"x-default" : "test3" }) # covers line 564 success path
878
+
879
+ assert xmp .dc_contributor == ["test1" ]
880
+ assert xmp .dc_creator == ["test2" ]
881
+ assert xmp .dc_title == {"x-default" : "test3" }
0 commit comments