1# Created: 30.04.2011, 2018 rewritten for pytest
2# Copyright (C) 2011-2019, Manfred Moitzi
3# License: MIT License
4import pytest
5from io import StringIO
6
7from ezdxf.lldxf.tags import Tags, DXFTag
8from ezdxf.lldxf.extendedtags import ExtendedTags
9from ezdxf import DXFKeyError, DXFValueError
10from ezdxf.lldxf.tagwriter import TagWriter
11
12
13@pytest.fixture
14def xtags1():
15    return ExtendedTags.from_text(XTAGS1)
16
17
18def test_init_appdata(xtags1):
19    assert xtags1.get_app_data('{ACAD_XDICTIONARY') is not None
20
21
22def test_init_with_tags():
23    tags = Tags.from_text(XTAGS1)
24    xtags = ExtendedTags(tags)
25    assert 3 == len(xtags.subclasses)
26    assert 1 == len(xtags.xdata)
27
28
29def test_init_xdata(xtags1):
30    assert xtags1.get_xdata('RAK') is not None
31
32
33def test_init_one_tag():
34    xtags = ExtendedTags([DXFTag(0, 'SECTION')])
35    assert xtags.noclass[0] == (0, 'SECTION')
36
37
38def test_getitem(xtags1):
39    assert xtags1[0] == xtags1.noclass[0]
40
41
42def test_appdata_content_count(xtags1):
43    xdict = xtags1.get_app_data('{ACAD_XDICTIONARY')
44    assert 3 == len(xdict)
45
46
47def test_appdata_content(xtags1):
48    xdict = xtags1.get_app_data('{ACAD_XDICTIONARY')
49    assert xdict.get_first_value(360) == "63D5"
50
51
52def test_tags_skips_appdata_content(xtags1):
53    with pytest.raises(DXFValueError):
54        xtags1.noclass.get_first_value(360)
55
56
57def test_xdata_content_count(xtags1):
58    rak = xtags1.get_xdata('RAK')
59    assert 17 == len(rak)
60
61
62def test_tags_skips_xdata_content(xtags1):
63    with pytest.raises(DXFValueError):
64        xtags1.noclass.get_first_value(1000)
65
66
67def test_copy(xtags1):
68    stream = StringIO()
69    tagwriter = TagWriter(stream)
70    tagwriter.write_tags(xtags1)
71    assert XTAGS1 == stream.getvalue()
72    stream.close()
73
74
75def test_getitem_layer(xtags1):
76    assert xtags1.noclass[0] == (0, 'LAYER')
77
78
79def test_getitem_xdict(xtags1):
80    assert xtags1.noclass[2] == (102, 0)
81
82
83def test_getitem_parent(xtags1):
84    assert xtags1.noclass[3] == (330, '18')
85
86
87def test_get_last_item(xtags1):
88    assert xtags1.noclass[-1] == (330, '18')
89
90
91def test_tagscount(xtags1):
92    """ apdata counts as one tag and xdata counts as one tag. """
93    assert 4 == len(xtags1.noclass)
94
95
96def test_subclass_AcDbSymbolTableRecord(xtags1):
97    subclass = xtags1.get_subclass('AcDbSymbolTableRecord')
98    assert 1 == len(subclass)
99
100
101def test_subclass_AcDbLayerTableRecord(xtags1):
102    subclass = xtags1.get_subclass('AcDbLayerTableRecord')
103    assert 8 == len(subclass)
104
105
106def test_clone_is_equal(xtags1):
107    clone = xtags1.clone()
108    assert xtags1 is not clone
109    assert xtags1.appdata is not clone.appdata
110    assert xtags1.subclasses is not clone.subclasses
111    assert xtags1.xdata is not clone.xdata
112    assert list(xtags1) == list(clone)
113
114
115def test_replace_handle(xtags1):
116    xtags1.replace_handle('AA')
117    assert 'AA' == xtags1.get_handle()
118
119
120XTAGS1 = """  0
121LAYER
122  5
1237
124102
125{ACAD_XDICTIONARY
126360
12763D5
128102
129}
130330
13118
132100
133AcDbSymbolTableRecord
134100
135AcDbLayerTableRecord
136  2
1370
138 70
1390
140 62
1417
142  6
143CONTINUOUS
144370
145-3
146390
1478
148347
149805
1501001
151RAK
1521000
153{75-LÄNGENSCHNITT-14
1541070
1550
1561070
1577
1581000
159CONTINUOUS
1601071
161-3
1621071
1631
1641005
1658
1661000
16775-LÄNGENSCHNITT-14}
1681000
169{75-LÄNGENSCHNITT-2005
1701070
1710
1721070
1737
1741000
175CONTINUOUS
1761071
177-3
1781071
1791
1801005
1818
1821000
18375-LÄNGENSCHNITT-2005}
184"""
185
186
187@pytest.fixture
188def xtags2():
189    return ExtendedTags.from_text(XTAGS2)
190
191
192def test_xdata_count(xtags2):
193    assert 3 == len(xtags2.xdata)
194
195
196def test_tags_count(xtags2):
197    """ 3 xdata chunks and two 'normal' tag. """
198    assert 2 == len(xtags2.noclass)
199
200
201def test_xdata3_tags(xtags2):
202    xdata = xtags2.get_xdata('XDATA3')
203    assert xdata[0] == (1001, 'XDATA3')
204    assert xdata[1] == (1000, 'TEXT-XDATA3')
205    assert xdata[2] == (1070, 2)
206    assert xdata[3] == (1070, 3)
207
208
209def test_new_data(xtags2):
210    xtags2.new_xdata('NEWXDATA', [(1000, 'TEXT')])
211    assert xtags2.has_xdata('NEWXDATA') is True
212
213    xdata = xtags2.get_xdata('NEWXDATA')
214    assert xdata[0] == (1001, 'NEWXDATA')
215    assert xdata[1] == (1000, 'TEXT')
216
217
218def test_set_new_data(xtags2):
219    xtags2.new_xdata('NEWXDATA', tags=[(1000, "Extended Data String")])
220    assert xtags2.has_xdata('NEWXDATA') is True
221
222    xdata = xtags2.get_xdata('NEWXDATA')
223    assert (1001, 'NEWXDATA') == xdata[0]
224    assert (1000, "Extended Data String") == xdata[1]
225
226
227def test_append_xdata(xtags2):
228    xdata = xtags2.get_xdata('MOZMAN')
229    assert 4 == len(xdata)
230
231    xdata.append(DXFTag(1000, "Extended Data String"))
232    xdata = xtags2.get_xdata('MOZMAN')
233    assert 5 == len(xdata)
234
235    assert DXFTag(1000, "Extended Data String") == xdata[4]
236
237
238XTAGS2 = """  0
239LAYER
240  5
2417
2421001
243RAK
2441000
245TEXT-RAK
2461070
2471
2481070
2491
2501001
251MOZMAN
2521000
253TEXT-MOZMAN
2541070
2552
2561070
2572
2581001
259XDATA3
2601000
261TEXT-XDATA3
2621070
2632
2641070
2653
266"""
267
268
269@pytest.fixture
270def xtags3():
271    return ExtendedTags.from_text(SPECIALCASE_TEXT)
272
273
274def test_read_tags(xtags3):
275    subclass2 = xtags3.get_subclass('AcDbText')
276    assert (100, 'AcDbText') == subclass2[0]
277
278
279def test_read_tags_2(xtags3):
280    subclass2 = xtags3.get_subclass('AcDbText')
281    assert (100, 'AcDbText') == subclass2[0]
282    assert (1, 'Title:') == subclass2[3]
283
284
285def test_read_tags_3(xtags3):
286    subclass2 = xtags3.get_subclass('AcDbText', 3)
287    assert (100, 'AcDbText') == subclass2[0]
288    assert (73, 2) == subclass2[1]
289
290
291def test_key_error(xtags3):
292    with pytest.raises(DXFKeyError):
293        xtags3.get_subclass('AcDbText', pos=4)
294
295
296def test_skip_empty_subclass(xtags3):
297    xtags3.subclasses[1] = Tags()  # create empty subclass
298    subclass2 = xtags3.get_subclass('AcDbText')
299    assert (100, 'AcDbText') == subclass2[0]
300
301
302SPECIALCASE_TEXT = """  0
303TEXT
3045
3058C9
306330
3076D
308100
309AcDbEntity
3108
3110
312100
313AcDbText
31410
3154.30
31620
3171.82
31830
3190.0
32040
3210.125
3221
323Title:
32441
3250.85
3267
327ARIALNARROW
328100
329AcDbText
33073
3312
332"""
333
334ACAD_REACTORS = '{ACAD_REACTORS'
335
336
337@pytest.fixture
338def xtags4():
339    return ExtendedTags.from_text(NO_REACTORS)
340
341
342def test_get_not_existing_reactor(xtags4):
343    with pytest.raises(DXFValueError):
344        xtags4.get_app_data(ACAD_REACTORS)
345
346
347def test_new_reactors(xtags4):
348    xtags4.new_app_data(ACAD_REACTORS)
349    assert (102, 0) == xtags4.noclass[-1]  # code = 102, value = index in appdata list
350
351
352def test_append_not_existing_reactors(xtags4):
353    xtags4.new_app_data(ACAD_REACTORS, [DXFTag(330, 'DEAD')])
354    reactors = xtags4.get_app_data_content(ACAD_REACTORS)
355    assert 1 == len(reactors)
356    assert DXFTag(330, 'DEAD') == reactors[0]
357
358
359def test_append_to_existing_reactors(xtags4):
360    xtags4.new_app_data(ACAD_REACTORS, [DXFTag(330, 'DEAD')])
361    reactors = xtags4.get_app_data_content(ACAD_REACTORS)
362    reactors.append(DXFTag(330, 'DEAD2'))
363    xtags4.set_app_data_content(ACAD_REACTORS, reactors)
364
365    reactors = xtags4.get_app_data_content(ACAD_REACTORS)
366    assert DXFTag(330, 'DEAD') == reactors[0]
367    assert DXFTag(330, 'DEAD2') == reactors[1]
368
369
370NO_REACTORS = """  0
371TEXT
372  5
3738C9
374330
3756D
376100
377AcDbEntity
378  8
3790
380100
381AcDbText
382 10
3834.30
384 20
3851.82
386 30
3870.0
388 40
3890.125
390  1
391Title:
392 41
3930.85
394  7
395ARIALNARROW
396"""
397
398
399def test_legacy_mode():
400    """ Legacy mode does the same job as filter_subclass_markers(). """
401    tags = ExtendedTags.from_text(LEICA_DISTO_TAGS, legacy=True)
402    assert 9 == len(tags.noclass)
403    assert 1 == len(tags.subclasses)
404    assert tags.noclass[0] == (0, 'LINE')
405    assert tags.noclass[1] == (8, 'LEICA_DISTO_3D')
406    assert tags.noclass[-1] == (210, (0, 0, 1))
407
408
409LEICA_DISTO_TAGS = """0
410LINE
411100
412AcDbEntity
4138
414LEICA_DISTO_3D
41562
416256
4176
418ByLayer
4195
42075
421100
422AcDbLine
42310
4240.819021
42520
426-0.633955
42730
428-0.273577
42911
4300.753216
43121
432-0.582009
43331
434-0.276937
43539
4360
437210
4380
439220
4400
441230
4421
443"""
444
445
446def test_group_code_1000_outside_XDATA():
447    tags = ExtendedTags(Tags.from_text(BLOCKBASEPOINTPARAMETER_CVIL_3D_2018))
448    assert tags.dxftype() == 'BLOCKBASEPOINTPARAMETER'
449    assert len(tags.subclasses) == 6
450    block_base_point_parameter = tags.get_subclass('AcDbBlockBasepointParameter')
451    assert len(block_base_point_parameter) == 3
452    assert block_base_point_parameter[0] == (100, 'AcDbBlockBasepointParameter')
453    assert block_base_point_parameter[1] == (1011, (0., 0., 0.))
454    assert block_base_point_parameter[2] == (1012, (0., 0., 0.))
455
456    block_element = tags.get_subclass('AcDbBlockElement')
457    assert block_element[4] == (1071, 0)
458
459    stream = StringIO()
460    tagwriter = TagWriter(stream)
461    tagwriter.write_tags(tags)
462    lines = stream.getvalue()
463    stream.close()
464    assert len(lines.split('\n')) == len(BLOCKBASEPOINTPARAMETER_CVIL_3D_2018.split('\n'))
465
466
467BLOCKBASEPOINTPARAMETER_CVIL_3D_2018 = """0
468BLOCKBASEPOINTPARAMETER
4695
4704C25
471330
4724C23
473100
474AcDbEvalExpr
47590
4761
47798
47833
47999
4804
481100
482AcDbBlockElement
483300
484Base Point
48598
48633
48799
4884
4891071
4900
491100
492AcDbBlockParameter
493280
4941
495281
4960
497100
498AcDbBlock1PtParameter
4991010
500-3.108080399920343
5011020
502-0.9562299080084814
5031030
5040.0
50593
5060
507170
5080
509171
5100
511100
512AcDbBlockBasepointParameter
5131011
5140.0
5151021
5160.0
5171031
5180.0
5191012
5200.0
5211022
5220.0
5231032
5240.0
525"""
526
527
528def test_xrecord_with_group_code_102():
529    tags = ExtendedTags(Tags.from_text(XRECORD_WITH_GROUP_CODE_102))
530    assert tags.dxftype() == 'XRECORD'
531    assert len(tags.appdata) == 1
532    assert tags.noclass[2] == (102, 0)  # 0 == index in appdata list
533    assert tags.appdata[0][0] == (102, '{ACAD_REACTORS')
534
535    xrecord = tags.get_subclass('AcDbXrecord')
536    assert xrecord[2] == (102, 'ACAD_ROUNDTRIP_PRE2007_TABLESTYLE')
537    assert len(list(tags)) * 2 + 1 == len(XRECORD_WITH_GROUP_CODE_102.split('\n'))  # +1 == appending '\n'
538
539
540XRECORD_WITH_GROUP_CODE_102 = """0
541XRECORD
5425
543D9B071D01A0CB6A5
544102
545{ACAD_REACTORS
546330
547D9B071D01A0CB69D
548102
549}
550330
551D9B071D01A0CB69D
552100
553AcDbXrecord
554280
555 1
556102
557ACAD_ROUNDTRIP_PRE2007_TABLESTYLE
55890
559    4
56091
561    0
5621
563
56492
565    4
56693
567    0
5682
569
57094
571    4
57295
573    0
5743
575
576"""
577
578
579def test_xrecord_with_long_closing_tag():
580    tags = ExtendedTags(Tags.from_text(XRECORD_APP_DATA_LONG_CLOSING_TAG))
581    assert tags.dxftype() == 'XRECORD'
582    # real app data just exists only in the base class, app data marker in AcDbXrecord are just tags, interpreted by
583    # the associated application
584    assert len(tags.appdata) == 1
585    assert len(tags.subclasses[1]) == 35
586
587
588XRECORD_APP_DATA_LONG_CLOSING_TAG = """  0
589XRECORD
5905
5912F9
592102
593{ACAD_REACTORS
594330
5952FF
596102
597}
598330
5992FF
600100
601AcDbXrecord
602280
6031
6041
605AcDb_Thumbnail_Schema
606102
607{ATTRRECORD
608341
6092FA
610102
611USUAL_102_TAG_INSIDE_APP_DATA
6122
613AcDbDs::TreatedAsObjectData
614280
6151
616291
6171
618102
619ATTRRECORD}
620102
621USUAL_102_TAG_OUTSIDE_APP_DATA
622102
623{ATTRRECORD
624341
6252FB
6262
627AcDbDs::Legacy
628280
6291
630291
6311
632102
633ATTRRECORD}
6342
635AcDbDs::ID
636280
63710
63891
6398
640102
641{ATTRRECORD
642341
6432FC
6442
645AcDs:Indexable
646280
6471
648291
6491
650102
651ATTRRECORD}
652102
653{ATTRRECORD
654341
6552FD
6562
657AcDbDs::HandleAttribute
658280
6597
660282
6611
662102
663ATTRRECORD}
6642
665Thumbnail_Data
666280
66715
66891
6690
670"""
671