# Purpose: Create a structured HTML view of the DXF tags - not a CAD drawing! # Created: 20.05.13 # Copyright (c) 2013-2018, Manfred Moitzi # License: MIT License """ Creates a structured HTML view of the DXF tags - not a CAD drawing! """ import os from typing import TYPE_CHECKING, Sequence, Tuple, Iterable, Dict, Set from ezdxf.lldxf.types import tag_type, is_point_code, is_pointer_code, is_binary_data from ezdxf.lldxf.types import GROUP_MARKERS, HEX_HANDLE_CODES, HANDLE_CODES, BINARY_FLAGS from ezdxf.tools import escape from ezdxf.sections.sections import KNOWN_SECTIONS from ezdxf.lldxf.packedtags import PackedTags from .reflinks import get_reference_link if TYPE_CHECKING: # import forward declarations from ezdxf.eztypes import Drawing, Table, DXFEntity, ExtendedTags, DXFTag # Tag groups # HTML templates # Section ALL_SECTIONS_TPL = '
\n{content}\n
' COMMON_SECTION_TPL = '
' \ '
SECTION: {ref_link}
\n' \ '
{prev} {next} top
\n' \ '{{content}}\n
\n' HEADER_SECTION_TPL = '
\n{content}\n
' TABLES_SECTION_TPL = '
{content}
' BLOCKS_SECTION_TPL = '
\n{content}\n
' # Section content HEADER_VAR_TPL = '
{code} {type}' \ ' {value}
' CUSTOM_VAR_TPL = '
Custom Property: {tag} ::' \ ' {value}
' TABLE_TPL = '
\n' \ '
{ref_link}
\n{nav}\n{header}\n{entries}\n
' ENTITIES_TPL = '
\n{}\n
' # DXF Entities ENTITY_TPL = '
{name}
\n{references} {tags}\n
' BLOCK_TPL = '
\n
{name}
\n{block}\n{entities}\n{endblk}\n
' TAG_LIST_TPL = '
\n{content}\n
' # Basic Tags TAG_TPL = '
{code} {type}' \ ' {value}
' TAG_HANDLE_DEF_TPL = '
{code}' \ ' {type} #{value}
' TAG_VALID_LINK_TPL = '
{code} {type}' \ ' #{value}
' TAG_INVALID_LINK_TPL = '
{code} {type}' \ ' #{value} [does not exist]
' MARKER_TPL = '
{tag}
' CONTROL_TPL = '
{tag}
' # Links SECTION_LINKS_TPL = '
{buttons}
\n' REF_LINK_TPL = '{name}' BUTTON_BAR_TPL = '
{content}
' BUTTON_TPL = '{name}' MAX_STR_LEN = 100 def build_ref_link_button(name: str) -> str: """Create a link-button for element *name* to the DXF reference. """ link = get_reference_link(name) return REF_LINK_TPL.format(target=link, name=name) TAG_TYPES = { int: '', float: '', str: '', } def tag_type_str(code: int) -> str: if code in GROUP_MARKERS: return '' elif code in HEX_HANDLE_CODES: return '' elif is_point_code(code): return '' elif is_binary_data(code): return '' else: return TAG_TYPES[tag_type(code)] def with_bitmask(value: int) -> str: return "{0}, b{0:08b}".format(int(value)) class DXF2HtmlConverter: def __init__(self, drawing: 'Drawing'): self.drawing = drawing self.entitydb = drawing.entitydb self.section_names_in_write_order = self._section_names_in_write_order() self.existing_pointers = self.collect_all_pointers() def _section_names_in_write_order(self) -> Sequence[str]: sections = self.drawing.sections write_order = list(name for name in KNOWN_SECTIONS if name in sections) write_order.extend(frozenset(sections.names()) - frozenset(KNOWN_SECTIONS)) return write_order def dxf2html(self) -> str: """Creates a structured HTML view of the DXF tags - not a CAD drawing! """ def get_name() -> str: if self.drawing.filename is None: return "unknown" else: filename = os.path.basename(self.drawing.filename) return os.path.splitext(filename)[0] template = load_resource('dxfpp.html') return template.format( name=get_name(), css=load_resource('dxfpp.css'), javascript=load_resource('dxfpp.js'), dxf_file=self.sections2html(), section_links=self.sections_link_bar(), ) def sections2html(self) -> str: """Creates a
container of all DXF sections. """ sections_html = [] sections = self.drawing.sections for section_name in self.section_names_in_write_order: section = sections.get(section_name) if section is not None: section_template = self.create_section_html_template(section.name) sections_html.append(self.section2html(section, section_template)) return ALL_SECTIONS_TPL.format(content="\n".join(sections_html)) def create_section_html_template(self, name: str) -> str: """Creates a section template with buttons to the previous and next section. """ def nav_targets() -> Tuple[str, str]: section_names = self.section_names_in_write_order index = section_names.index(name) prev_index = max(0, index - 1) succ_index = min(len(section_names) - 1, index + 1) return section_names[prev_index], section_names[succ_index] prev_id, next_id = nav_targets() prev_button = BUTTON_TPL.format(target=prev_id, name='previous') next_button = BUTTON_TPL.format(target=next_id, name='next') return COMMON_SECTION_TPL.format( ref_link=build_ref_link_button(name.upper()), this_id=name, prev=prev_button, next=next_button) def sections_link_bar(self) -> str: """Creates a
container as link bar to all DXF sections. """ section_links = [] for section_name in self.section_names_in_write_order: section_links.append(BUTTON_TPL.format( name=section_name.upper(), target=section_name )) return SECTION_LINKS_TPL.format(buttons=' \n'.join(section_links)) def entities(self) -> Iterable['DXFEntity']: return iter(self.drawing.entities) def section2html(self, section, section_template: str) -> str: """Creates a
container of a specific DXF sections. """ if section.name == 'HEADER': return section_template.format(content=self.hdrvars2html(section.hdrvars, section.custom_vars)) elif section.name == 'ENTITIES': return section_template.format(content=self.entities2html(self.entities(), create_ref_links=True)) elif section.name == 'CLASSES': return section_template.format(content=self.entities2html(section.classes.values()), create_ref_links=False, show_ref_status=False) elif section.name == 'OBJECTS': return section_template.format(content=self.entities2html(iter(section), create_ref_links=True, show_ref_status=True)) elif section.name == 'TABLES': return section_template.format(content=self.tables2html(section)) # no iterator! elif section.name == 'BLOCKS': return section_template.format(content=self.blocks2html(iter(section))) else: return section_template.format(content=self.tags2html(section.tags())) @staticmethod def hdrvars2html(hdrvars, custom_vars) -> str: """DXF header section as
container. """ def vartype(hdrvar) -> str: if is_point_code(hdrvar.code): dim = len(hdrvar.value) - 2 return ("", "")[dim] else: return tag_type_str(hdrvar.code) varstrings = [ HEADER_VAR_TPL.format(code=name, value=escape(str(hdrvar.value)), type=escape(vartype(hdrvar))) for name, hdrvar in hdrvars.items() ] custom_property_strings = [ CUSTOM_VAR_TPL.format(tag=escape(str(tag)), value=escape(str(value))) for tag, value in custom_vars ] varstrings.extend(custom_property_strings) return HEADER_SECTION_TPL.format(content="\n".join(varstrings)) def entities2html(self, entities: Iterable['DXFEntity'], create_ref_links=False, show_ref_status=False) -> str: """DXF entities as
container. """ entity_strings = (self.entity2html(entity, create_ref_links, show_ref_status) for entity in entities) return ENTITIES_TPL.format("\n".join(entity_strings)) def entity2html(self, entity: 'DXFEntity', create_ref_links=False, show_ref_status=False): """DXF entity as
container. """ tags = entity.tags name = entity.dxftype() if create_ref_links: # use entity name as link to the DXF reference name = build_ref_link_button(name) refs = "" if show_ref_status: handle = tags.get_handle() if handle not in self.existing_pointers: refs = '
[unreferenced]
' else: refs = self.pointers2html(self.existing_pointers[handle]) return ENTITY_TPL.format(name=name, tags=self.tags2html(tags), references=refs) def pointers2html(self, pointers: Iterable[str]) -> str: pointers_str = ", ".join(('{value}'.format(value=ptr) for ptr in sorted(pointers, key=lambda x: int(x, 16)))) return '
referenced by: {pointers}
'.format(pointers=pointers_str) def tags2html(self, tags: 'ExtendedTags') -> str: """DXF tag list as
container. """ def tag2html(tag: 'DXFTag') -> str: def trim_str(vstr: str) -> str: if len(vstr) > MAX_STR_LEN: vstr = vstr[:(MAX_STR_LEN - 15)] + " ... " + vstr[-10:] return vstr if isinstance(tag, PackedTags): # inflate packed tags return ''.join(tag2html(tag) for tag in tag.dxftags()) tpl = TAG_TPL if tag.code in HANDLE_CODES: # is handle definition tpl = TAG_HANDLE_DEF_TPL elif is_pointer_code(tag.code): # is handle link if tag.value in self.entitydb: tpl = TAG_VALID_LINK_TPL else: tpl = TAG_INVALID_LINK_TPL if tag.code in BINARY_FLAGS: vstr = with_bitmask(tag.value) else: if hasattr(tag, 'tostring'): s = tag.tostring() else: s = str(tag.value) vstr = trim_str(s) type_str = tag_type_str(tag.code) return tpl.format(code=tag.code, value=escape(vstr), type=escape(type_str)) def group_marker(tag: 'DXFTag', tag_html: str) -> str: return tag_html if tag.code not in GROUP_MARKERS else MARKER_TPL.format(tag=tag_html) expanded_tags = self.expand_linked_tags(tags) tag_strings = (group_marker(tag, tag2html(tag)) for tag in expanded_tags) return TAG_LIST_TPL.format(content='\n'.join(tag_strings)) def tables2html(self, tables: Iterable['Table']) -> str: """DXF tables section as
container. """ navigation = self.create_table_navigation(tables) tables_html_strings = [self.table2html(table, navigation) for table in tables] return TABLES_SECTION_TPL.format(content='\n'.join(tables_html_strings)) @staticmethod def create_table_navigation(table_section: Iterable['Table']) -> str: """Create a button bar with links to all DXF tables as
container. """ buttons = [] for table in table_section: name = table.name.upper() link = "{}-table".format(name) buttons.append(BUTTON_TPL.format(name=name, target=link)) return BUTTON_BAR_TPL.format(content="\n".join(buttons)) def table2html(self, table: 'Table', navigation='') -> str: """DXF table as
container. """ header = ENTITY_TPL.format(name="TABLE HEADER", tags=self.tags2html(table._table_header), references="") entries = self.entities2html(table) table_name = table.name.upper() return TABLE_TPL.format(name=table_name, ref_link=build_ref_link_button(table_name), nav=navigation, header=header, entries=entries) def expand_linked_tags(self, tags: 'ExtendedTags') -> 'DXFTag': while True: yield from tags if not hasattr(tags, 'link') or tags.link is None: return tags = self.entitydb[tags.link] def collect_all_pointers(self) -> Dict[str, Set[str]]: existing_pointers = dict() for tags in self.entitydb.values(): handle = tags.get_handle() for tag in self.expand_linked_tags(tags): if is_pointer_code(tag.code): pointers = existing_pointers.setdefault(tag.value, set()) pointers.add(handle) return existing_pointers def blocks2html(self, blocks: Iterable) -> str: """DXF blocks section as
container. """ block_strings = (self.block2html(block) for block in blocks) return BLOCKS_SECTION_TPL.format(content='\n'.join(block_strings)) def block2html(self, block_layout) -> str: """DXF block entity as
container. """ block_html = self.entity2html(block_layout.block, create_ref_links=True) if block_layout.name.upper() not in ('*MODEL_SPACE', '*PAPER_SPACE'): entities_html = self.entities2html(iter(block_layout), create_ref_links=True) else: entities_html = '' endblk_html = self.entity2html(block_layout.endblk, create_ref_links=True) return BLOCK_TPL.format(name=block_layout.name, block=block_html, entities=entities_html, endblk=endblk_html) def dxfpp(drawing: 'Drawing') -> str: """Creates a structured HTML view of the DXF tags - not a CAD drawing! """ return DXF2HtmlConverter(drawing).dxf2html() def load_resource(filename: str) -> str: """Load external resource files. """ src_path = os.path.dirname(__file__) src = os.path.join(src_path, filename) try: with open(src, mode="rt", encoding='utf-8') as fp: resource = fp.read() except IOError: errmsg = "IOError: can not read file '{}'.".format(src) print(errmsg) resource = errmsg return resource