refactor: excel parse
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
# Copyright (c) 2011-2022, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from __future__ import annotations
|
||||
from typing import (
|
||||
Any,
|
||||
Iterable,
|
||||
Iterator,
|
||||
KeysView,
|
||||
Optional,
|
||||
Sequence,
|
||||
TYPE_CHECKING,
|
||||
Union,
|
||||
)
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from ezdxf.lldxf import const
|
||||
from ezdxf.lldxf.tags import group_tags, Tags, DXFTag
|
||||
from ezdxf.lldxf.types import strtag
|
||||
from ezdxf.lldxf.validator import header_validator
|
||||
from ezdxf.sections.headervars import (
|
||||
HEADER_VAR_MAP,
|
||||
version_specific_group_code,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.lldxf.tagwriter import AbstractTagWriter
|
||||
|
||||
logger = logging.getLogger("ezdxf")
|
||||
|
||||
MIN_HEADER_TEXT = """ 0
|
||||
SECTION
|
||||
2
|
||||
HEADER
|
||||
9
|
||||
$ACADVER
|
||||
1
|
||||
AC1009
|
||||
9
|
||||
$DWGCODEPAGE
|
||||
3
|
||||
ANSI_1252
|
||||
9
|
||||
$HANDSEED
|
||||
5
|
||||
FF
|
||||
"""
|
||||
|
||||
# Additional variables may be stored as DICTIONARYVAR in the OBJECTS
|
||||
# section in the DICTIONARY "AcDbVariableDictionary" of the root dict.
|
||||
# - CANNOSCALE
|
||||
# - CENTEREXE
|
||||
# - CENTERLTYPEFILE
|
||||
# - CETRANSPARENCY
|
||||
# - CMLEADERSTYLE
|
||||
# - CTABLESTYLE
|
||||
# - CVIEWDETAILSTYLE
|
||||
# - CVIEWSECTIONSTYLE
|
||||
# - LAYEREVAL
|
||||
# - LAYERNOTIFY
|
||||
# - LIGHTINGUNITS
|
||||
# - MSLTSCALE
|
||||
|
||||
|
||||
class CustomVars:
|
||||
"""The :class:`CustomVars` class stores custom properties in the DXF header as
|
||||
$CUSTOMPROPERTYTAG and $CUSTOMPROPERTY values. Custom properties require DXF R2004
|
||||
or later, `ezdxf` can create custom properties for older DXF versions as well, but
|
||||
AutoCAD will not show that properties.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.properties: list[tuple[str, str]] = list()
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Count of custom properties."""
|
||||
return len(self.properties)
|
||||
|
||||
def __iter__(self) -> Iterator[tuple[str, str]]:
|
||||
"""Iterate over all custom properties as ``(tag, value)`` tuples."""
|
||||
return iter(self.properties)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove all custom properties."""
|
||||
self.properties.clear()
|
||||
|
||||
def append(self, tag: str, value: str) -> None:
|
||||
"""Add custom property as ``(tag, value)`` tuple."""
|
||||
# custom properties always stored as strings
|
||||
self.properties.append((tag, str(value)))
|
||||
|
||||
def get(self, tag: str, default: Optional[str] = None):
|
||||
"""Returns the value of the first custom property `tag`."""
|
||||
for key, value in self.properties:
|
||||
if key == tag:
|
||||
return value
|
||||
else:
|
||||
return default
|
||||
|
||||
def has_tag(self, tag: str) -> bool:
|
||||
"""Returns ``True`` if custom property `tag` exist."""
|
||||
return self.get(tag) is not None
|
||||
|
||||
def remove(self, tag: str, all: bool = False) -> None:
|
||||
"""Removes the first occurrence of custom property `tag`, removes all
|
||||
occurrences if `all` is ``True``.
|
||||
|
||||
Raises `:class:`DXFValueError` if `tag` does not exist.
|
||||
|
||||
"""
|
||||
found_tag = False
|
||||
for item in self.properties:
|
||||
if item[0] == tag:
|
||||
self.properties.remove(item)
|
||||
found_tag = True
|
||||
if not all:
|
||||
return
|
||||
if not found_tag:
|
||||
raise const.DXFValueError(f"Tag '{tag}' does not exist")
|
||||
|
||||
def replace(self, tag: str, value: str) -> None:
|
||||
"""Replaces the value of the first custom property `tag` by a new
|
||||
`value`.
|
||||
|
||||
Raises :class:`DXFValueError` if `tag` does not exist.
|
||||
|
||||
"""
|
||||
properties = self.properties
|
||||
for index in range(len(properties)):
|
||||
name = properties[index][0]
|
||||
if name == tag:
|
||||
properties[index] = (name, value)
|
||||
return
|
||||
|
||||
raise const.DXFValueError(f"Tag '{tag}' does not exist")
|
||||
|
||||
def write(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Export custom properties as DXF tags. (internal API)"""
|
||||
for tag, value in self.properties:
|
||||
s = f" 9\n$CUSTOMPROPERTYTAG\n 1\n{tag}\n 9\n$CUSTOMPROPERTY\n 1\n{value}\n"
|
||||
tagwriter.write_str(s)
|
||||
|
||||
|
||||
def default_vars() -> OrderedDict:
|
||||
vars = OrderedDict()
|
||||
for vardef in HEADER_VAR_MAP.values():
|
||||
vars[vardef.name] = HeaderVar(DXFTag(vardef.code, vardef.default))
|
||||
return vars
|
||||
|
||||
|
||||
class HeaderSection:
|
||||
MIN_HEADER_TAGS = Tags.from_text(MIN_HEADER_TEXT)
|
||||
name = "HEADER"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.hdrvars: dict[str, HeaderVar] = OrderedDict()
|
||||
self.custom_vars = CustomVars()
|
||||
|
||||
@classmethod
|
||||
def load(cls, tags: Optional[Iterable[DXFTag]] = None) -> HeaderSection:
|
||||
"""Constructor to generate header variables loaded from DXF files
|
||||
(untrusted environment).
|
||||
|
||||
Args:
|
||||
tags: DXF tags as Tags() or ExtendedTags()
|
||||
|
||||
(internal API)
|
||||
"""
|
||||
if tags is None: # create default header
|
||||
# files without a header have the default version: R12
|
||||
return cls.new(dxfversion=const.DXF12)
|
||||
section = cls()
|
||||
section.load_tags(iter(tags))
|
||||
return section
|
||||
|
||||
@classmethod
|
||||
def new(cls, dxfversion=const.LATEST_DXF_VERSION) -> HeaderSection:
|
||||
section = HeaderSection()
|
||||
section.hdrvars = default_vars()
|
||||
section["$ACADVER"] = dxfversion
|
||||
return section
|
||||
|
||||
def load_tags(self, tags: Iterable[DXFTag]) -> None:
|
||||
"""Constructor to generate header variables loaded from DXF files
|
||||
(untrusted environment).
|
||||
|
||||
Args:
|
||||
tags: DXF tags as Tags() or ExtendedTags()
|
||||
|
||||
"""
|
||||
_tags = iter(tags) or iter(self.MIN_HEADER_TAGS)
|
||||
section_tag = next(_tags)
|
||||
name_tag = next(_tags)
|
||||
|
||||
if section_tag != (0, "SECTION") or name_tag != (2, "HEADER"):
|
||||
raise const.DXFStructureError("Critical structure error in HEADER section.")
|
||||
|
||||
groups = group_tags(header_validator(_tags), splitcode=9)
|
||||
custom_property_stack = [] # collect $CUSTOMPROPERTY/TAG
|
||||
for group in groups:
|
||||
name = group[0].value
|
||||
value = group[1]
|
||||
if name in ("$CUSTOMPROPERTYTAG", "$CUSTOMPROPERTY"):
|
||||
custom_property_stack.append(value.value)
|
||||
else:
|
||||
self.hdrvars[name] = HeaderVar(value)
|
||||
|
||||
custom_property_stack.reverse()
|
||||
while len(custom_property_stack):
|
||||
try:
|
||||
self.custom_vars.append(
|
||||
tag=custom_property_stack.pop(),
|
||||
value=custom_property_stack.pop(),
|
||||
)
|
||||
except IndexError: # internal exception
|
||||
break
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, text: str) -> HeaderSection:
|
||||
"""Load constructor from text for testing"""
|
||||
return cls.load(Tags.from_text(text))
|
||||
|
||||
def _headervar_factory(self, key: str, value: Any) -> DXFTag:
|
||||
if key in HEADER_VAR_MAP:
|
||||
factory = HEADER_VAR_MAP[key].factory
|
||||
return factory(value)
|
||||
else:
|
||||
raise const.DXFKeyError(f"Invalid header variable {key}.")
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Returns count of header variables."""
|
||||
return len(self.hdrvars)
|
||||
|
||||
def __contains__(self, key) -> bool:
|
||||
"""Returns ``True`` if header variable `key` exist."""
|
||||
return key in self.hdrvars
|
||||
|
||||
def varnames(self) -> KeysView:
|
||||
"""Returns an iterable of all header variable names."""
|
||||
return self.hdrvars.keys()
|
||||
|
||||
def export_dxf(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Exports header section as DXF tags. (internal API)"""
|
||||
|
||||
def _write(name: str, value: Any) -> None:
|
||||
if value.value is None:
|
||||
logger.info(f"did not write header var {name}, value is None.")
|
||||
return
|
||||
tagwriter.write_tag2(9, name)
|
||||
group_code = version_specific_group_code(name, dxfversion)
|
||||
# fix invalid group codes
|
||||
if group_code != value.code:
|
||||
value = HeaderVar((group_code, value.value))
|
||||
tagwriter.write_str(str(value))
|
||||
|
||||
dxfversion: str = tagwriter.dxfversion
|
||||
write_handles = tagwriter.write_handles
|
||||
|
||||
tagwriter.write_str(" 0\nSECTION\n 2\nHEADER\n")
|
||||
save = self["$ACADVER"]
|
||||
self["$ACADVER"] = dxfversion
|
||||
for name, value in header_vars_by_priority(self.hdrvars, dxfversion):
|
||||
if not write_handles and name == "$HANDSEED":
|
||||
continue # skip $HANDSEED
|
||||
_write(name, value)
|
||||
if name == "$LASTSAVEDBY": # ugly hack, but necessary for AutoCAD
|
||||
self.custom_vars.write(tagwriter)
|
||||
self["$ACADVER"] = save
|
||||
tagwriter.write_str(" 0\nENDSEC\n")
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""Returns value of header variable `key` if exist, else the `default`
|
||||
value.
|
||||
|
||||
"""
|
||||
if key in self.hdrvars:
|
||||
return self.__getitem__(key)
|
||||
else:
|
||||
return default
|
||||
|
||||
def __getitem__(self, key: str) -> Any:
|
||||
"""Get header variable `key` by index operator like:
|
||||
:code:`drawing.header['$ACADVER']`
|
||||
|
||||
"""
|
||||
try:
|
||||
return self.hdrvars[key].value
|
||||
except KeyError: # map exception
|
||||
raise const.DXFKeyError(str(key))
|
||||
|
||||
def __setitem__(self, key: str, value: Any) -> None:
|
||||
"""Set header variable `key` to `value` by index operator like:
|
||||
:code:`drawing.header['$ANGDIR'] = 1`
|
||||
|
||||
"""
|
||||
try:
|
||||
tags = self._headervar_factory(key, value)
|
||||
except (IndexError, ValueError):
|
||||
raise const.DXFValueError(str(value))
|
||||
self.hdrvars[key] = HeaderVar(tags)
|
||||
|
||||
def __delitem__(self, key: str) -> None:
|
||||
"""Delete header variable `key` by index operator like:
|
||||
:code:`del drawing.header['$ANGDIR']`
|
||||
|
||||
"""
|
||||
try:
|
||||
del self.hdrvars[key]
|
||||
except KeyError: # map exception
|
||||
raise const.DXFKeyError(str(key))
|
||||
|
||||
def reset_wcs(self):
|
||||
"""Reset the current UCS settings to the :ref:`WCS`."""
|
||||
self["$UCSBASE"] = ""
|
||||
self["$UCSNAME"] = ""
|
||||
self["$UCSORG"] = (0, 0, 0)
|
||||
self["$UCSXDIR"] = (1, 0, 0)
|
||||
self["$UCSYDIR"] = (0, 1, 0)
|
||||
self["$UCSORTHOREF"] = ""
|
||||
self["$UCSORTHOVIEW"] = 0
|
||||
self["$UCSORGTOP"] = (0, 0, 0)
|
||||
self["$UCSORGBOTTOM"] = (0, 0, 0)
|
||||
self["$UCSORGLEFT"] = (0, 0, 0)
|
||||
self["$UCSORGRIGHT"] = (0, 0, 0)
|
||||
self["$UCSORGFRONT"] = (0, 0, 0)
|
||||
self["$UCSORGBACK"] = (0, 0, 0)
|
||||
|
||||
|
||||
def header_vars_by_priority(
|
||||
header_vars: dict[str, HeaderVar], dxfversion: str
|
||||
) -> Iterable[tuple]:
|
||||
order = []
|
||||
for name, value in header_vars.items():
|
||||
vardef = HEADER_VAR_MAP.get(name, None)
|
||||
if vardef is None:
|
||||
logger.info(f"Header variable {name} ignored, dxfversion={dxfversion}.")
|
||||
continue
|
||||
if vardef.mindxf <= dxfversion <= vardef.maxdxf:
|
||||
order.append((vardef.priority, (name, value)))
|
||||
order.sort()
|
||||
for priority, tag in order:
|
||||
yield tag
|
||||
|
||||
|
||||
class HeaderVar:
|
||||
def __init__(self, tag: Union[DXFTag, Sequence]):
|
||||
self.tag = tag
|
||||
|
||||
@property
|
||||
def code(self) -> int:
|
||||
return self.tag[0]
|
||||
|
||||
@property
|
||||
def value(self) -> Any:
|
||||
return self.tag[1]
|
||||
|
||||
@property
|
||||
def ispoint(self) -> bool:
|
||||
return self.code == 10
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.ispoint:
|
||||
code, value = self.tag
|
||||
s = []
|
||||
for coord in value:
|
||||
s.append(strtag((code, coord)))
|
||||
code += 10
|
||||
return "".join(s)
|
||||
else:
|
||||
return strtag(self.tag) # type: ignore
|
||||
Reference in New Issue
Block a user