#!/usr/bin/env python # -*- coding: utf-8 -*- ''' Core Zen Coding library. Contains various text manipulation functions: == Expand abbreviation Expands abbreviation like ul#nav>li*5>a into a XHTML string. === How to use First, you have to extract current string (where cursor is) from your test editor and use find_abbr_in_line() method to extract abbreviation. If abbreviation was found, this method will return it as well as position index of abbreviation inside current line. If abbreviation wasn't found, method returns empty string. With abbreviation found, you should call parse_into_tree() method to transform abbreviation into a tag tree. This method returns Tag object on success, None on failure. Then simply call to_string() method of returned Tag object to transoform tree into a XHTML string You can setup output profile using setup_profile() method (see default_profile definition for available options) Created on Apr 17, 2009 @author: Sergey Chikuyonok (http://chikuyonok.ru) ''' from zen_settings import zen_settings import re import stparser newline = '\n' "Newline symbol" caret_placeholder = '{%::zen-caret::%}' default_tag = 'div' re_tag = re.compile(r'<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*\s*(\/?)>$') profiles = {} "Available output profiles" default_profile = { 'tag_case': 'lower', # values are 'lower', 'upper' 'attr_case': 'lower', # values are 'lower', 'upper' 'attr_quotes': 'double', # values are 'single', 'double' 'tag_nl': 'decide', # each tag on new line, values are True, False, 'decide' 'place_cursor': True, # place cursor char — | (pipe) — in output 'indent': True, # indent tags 'inline_break': 3, # how many inline elements should be to force line break (set to 0 to disable) 'self_closing_tag': 'xhtml' # use self-closing style for writing empty elements, e.g.
or
. # values are True, False, 'xhtml' } basic_filters = 'html'; "Filters that will be applied for unknown syntax" def char_at(text, pos): """ Returns character at specified index of text. If index if out of range, returns empty string """ return text[pos] if pos < len(text) else '' def has_deep_key(obj, key): """ Check if obj dictionary contains deep key. For example, example, it will allow you to test existance of my_dict[key1][key2][key3], testing existance of my_dict[key1] first, then my_dict[key1][key2], and finally my_dict[key1][key2][key3] @param obj: Dictionary to test @param obj: dict @param key: Deep key to test. Can be list (like ['key1', 'key2', 'key3']) or string (like 'key1.key2.key3') @type key: list, tuple, str @return: bool """ if isinstance(key, str): key = key.split('.') last_obj = obj for v in key: if hasattr(last_obj, v): last_obj = getattr(last_obj, v) elif last_obj.has_key(v): last_obj = last_obj[v] else: return False return True def is_allowed_char(ch): """ Test if passed symbol is allowed in abbreviation @param ch: Symbol to test @type ch: str @return: bool """ return ch.isalnum() or ch in "#.>+*:$-_!@[]()|" def split_by_lines(text, remove_empty=False): """ Split text into lines. Set remove_empty to true to filter out empty lines @param text: str @param remove_empty: bool @return list """ lines = text.splitlines() return remove_empty and [line for line in lines if line.strip()] or lines def make_map(prop): """ Helper function that transforms string into dictionary for faster search @param prop: Key name in zen_settings['html'] dictionary @type prop: str """ obj = {} for a in zen_settings['html'][prop].split(','): obj[a] = True zen_settings['html'][prop] = obj def create_profile(options): """ Create profile by adding default values for passed optoin set @param options: Profile options @type options: dict """ for k, v in default_profile.items(): options.setdefault(k, v) return options def setup_profile(name, options = {}): """ @param name: Profile name @type name: str @param options: Profile options @type options: dict """ profiles[name.lower()] = create_profile(options); def get_newline(): """ Returns newline symbol which is used in editor. This function must be redefined to return current editor's settings @return: str """ return newline def set_newline(char): """ Sets newline character used in Zen Coding """ global newline newline = char def string_to_hash(text): """ Helper function that transforms string into hash @return: dict """ obj = {} items = text.split(",") for i in items: obj[i] = True return obj def pad_string(text, pad): """ Indents string with space characters (whitespace or tab) @param text: Text to indent @type text: str @param pad: Indentation level (number) or indentation itself (string) @type pad: int, str @return: str """ pad_str = '' result = '' if isinstance(pad, basestring): pad_str = pad else: pad_str = get_indentation() * pad nl = get_newline() lines = split_by_lines(text) if lines: result += lines[0] for line in lines[1:]: result += nl + pad_str + line return result def is_snippet(abbr, doc_type = 'html'): """ Check is passed abbreviation is a snippet @return bool """ return get_snippet(doc_type, abbr) and True or False def is_ends_with_tag(text): """ Test is string ends with XHTML tag. This function used for testing if '<' symbol belogs to tag or abbreviation @type text: str @return: bool """ return re_tag.search(text) != None def get_elements_collection(resource, type): """ Returns specified elements collection (like 'empty', 'block_level') from resource. If collections wasn't found, returns empty object @type resource: dict @type type: str @return: dict """ if 'element_types' in resource and type in resource['element_types']: return resource['element_types'][type] else: return {} def replace_variables(text, vars=zen_settings['variables']): """ Replace variables like ${var} in string @param text: str @return: str """ return re.sub(r'\$\{([\w\-]+)\}', lambda m: m.group(1) in vars and vars[m.group(1)] or m.group(0), text) def get_abbreviation(res_type, abbr): """ Returns abbreviation value from data set @param res_type: Resource type (html, css, ...) @type res_type: str @param abbr: Abbreviation name @type abbr: str @return dict, None """ return get_settings_resource(res_type, abbr, 'abbreviations') def get_snippet(res_type, snippet_name): """ Returns snippet value from data set @param res_type: Resource type (html, css, ...) @type res_type: str @param snippet_name: Snippet name @type snippet_name: str @return dict, None """ return get_settings_resource(res_type, snippet_name, 'snippets'); def get_variable(name): """ Returns variable value @return: str """ return zen_settings['variables'][name] def set_variable(name, value): """ Set variable value """ zen_settings['variables'][name] = value def get_indentation(): """ Returns indentation string @return {String} """ return get_variable('indentation'); def create_resource_chain(syntax, name): """ Creates resource inheritance chain for lookups @param syntax: Syntax name @type syntax: str @param name: Resource name @type name: str @return: list """ result = [] if syntax in zen_settings: resource = zen_settings[syntax] if name in resource: result.append(resource[name]) if 'extends' in resource: # find resource in ancestors for type in resource['extends']: if has_deep_key(zen_settings, [type, name]): result.append(zen_settings[type][name]) return result def get_resource(syntax, name): """ Get resource collection from settings file for specified syntax. It follows inheritance chain if resource wasn't directly found in syntax settings @param syntax: Syntax name @type syntax: str @param name: Resource name @type name: str """ chain = create_resource_chain(syntax, name) return chain[0] if chain else None def get_settings_resource(syntax, abbr, name): """ Returns resurce value from data set with respect of inheritance @param syntax: Resource syntax (html, css, ...) @type syntax: str @param abbr: Abbreviation name @type abbr: str @param name: Resource name ('snippets' or 'abbreviation') @type name: str @return dict, None """ for item in create_resource_chain(syntax, name): if abbr in item: return item[abbr] return None def get_word(ix, text): """ Get word, starting at ix character of text @param ix: int @param text: str """ m = re.match(r'^[\w\-:\$]+', text[ix:]) return m.group(0) if m else '' def extract_attributes(attr_set): """ Extract attributes and their values from attribute set @param attr_set: str """ attr_set = attr_set.strip() loop_count = 100 # endless loop protection re_string = r'^(["\'])((?:(?!\1)[^\\]|\\.)*)\1' result = [] while attr_set and loop_count: loop_count -= 1 attr_name = get_word(0, attr_set) attr = None if attr_name: attr = {'name': attr_name, 'value': ''} # let's see if attribute has value ch = attr_set[len(attr_name)] if len(attr_set) > len(attr_name) else '' if ch == '=': ch2 = attr_set[len(attr_name) + 1] if ch2 in '"\'': # we have a quoted string m = re.match(re_string, attr_set[len(attr_name) + 1:]) if m: attr['value'] = m.group(2) attr_set = attr_set[len(attr_name) + len(m.group(0)) + 1:].strip() else: # something wrong, break loop attr_set = '' else: # unquoted string m = re.match(r'^(.+?)(\s|$)', attr_set[len(attr_name) + 1:]) if m: attr['value'] = m.group(1) attr_set = attr_set[len(attr_name) + len(m.group(1)) + 1:].strip() else: # something wrong, break loop attr_set = '' else: attr_set = attr_set[len(attr_name):].strip() else: # something wrong, can't extract attribute name break if attr: result.append(attr) return result def parse_attributes(text): """ Parses tag attributes extracted from abbreviation """ # Example of incoming data: # #header # .some.data # .some.data#header # [attr] # #item[attr=Hello other="World"].class result = [] class_name = None char_map = {'#': 'id', '.': 'class'} # walk char-by-char i = 0 il = len(text) while i < il: ch = text[i] if ch == '#': # id val = get_word(i, text[1:]) result.append({'name': char_map[ch], 'value': val}) i += len(val) + 1 elif ch == '.': #class val = get_word(i, text[1:]) if not class_name: # remember object pointer for value modification class_name = {'name': char_map[ch], 'value': ''} result.append(class_name) if class_name['value']: class_name['value'] += ' ' + val else: class_name['value'] = val i += len(val) + 1 elif ch == '[': # begin attribute set # search for end of set end_ix = text.find(']', i) if end_ix == -1: # invalid attribute set, stop searching i = len(text) else: result.extend(extract_attributes(text[i + 1:end_ix])) i = end_ix else: i += 1 return result class AbbrGroup(object): """ Abreviation's group element """ def __init__(self, parent=None): """ @param parent: Parent group item element @type parent: AbbrGroup """ self.expr = '' self.parent = parent self.children = [] def add_child(self): child = AbbrGroup(self) self.children.append(child) return child def clean_up(self): for item in self.children: expr = item.expr if not expr: self.children.remove(item) else: # remove operators at the and of expression item.clean_up() def split_by_groups(abbr): """ Split abbreviation by groups @type abbr: str @return: AbbrGroup """ root = AbbrGroup() last_parent = root cur_item = root.add_child() stack = [] i = 0 il = len(abbr) while i < il: ch = abbr[i] if ch == '(': # found new group operator = i and abbr[i - 1] or '' if operator == '>': stack.append(cur_item) last_parent = cur_item else: stack.append(last_parent) cur_item = None elif ch == ')': last_parent = stack.pop() cur_item = None next_char = char_at(abbr, i + 1) if next_char == '+' or next_char == '>': # next char is group operator, skip it i += 1 else: if ch == '+' or ch == '>': # skip operator if it's followed by parenthesis next_char = char_at(abbr, i + 1) if next_char == '(': i += 1 continue if not cur_item: cur_item = last_parent.add_child() cur_item.expr += ch i += 1 root.clean_up() return root def rollout_tree(tree, parent=None): """ Roll outs basic Zen Coding tree into simplified, DOM-like tree. The simplified tree, for example, represents each multiplied element as a separate element sets with its own content, if exists. The simplified tree element contains some meta info (tag name, attributes, etc.) as well as output strings, which are exactly what will be outputted after expanding abbreviation. This tree is used for filtering: you can apply filters that will alter output strings to get desired look of expanded abbreviation. @type tree: Tag @param parent: ZenNode """ if not parent: parent = ZenNode(tree) how_many = 1 tag_content = '' for child in tree.children: how_many = child.count if child.repeat_by_lines: # it's a repeating element tag_content = split_by_lines(child.get_content(), True) how_many = max(len(tag_content), 1) else: tag_content = child.get_content() for j in range(how_many): tag = ZenNode(child) parent.add_child(tag) if child.children: rollout_tree(child, tag) add_point = tag.find_deepest_child() or tag if tag_content: if isinstance(tag_content, basestring): add_point.content = tag_content else: add_point.content = tag_content[j] or '' return parent def run_filters(tree, profile, filter_list): """ Runs filters on tree @type tree: ZenNode @param profile: str, object @param filter_list: str, list @return: ZenNode """ import filters if isinstance(profile, basestring) and profile in profiles: profile = profiles[profile]; if not profile: profile = profiles['plain'] if isinstance(filter_list, basestring): filter_list = re.split(r'[\|,]', filter_list) for name in filter_list: name = name.strip() if name and name in filters.filter_map: tree = filters.filter_map[name](tree, profile) return tree def abbr_to_primary_tree(abbr, doc_type='html'): """ Transforms abbreviation into a primary internal tree. This tree should'n be used ouside of this scope @param abbr: Abbreviation to transform @type abbr: str @param doc_type: Document type (xsl, html), a key of dictionary where to search abbreviation settings @type doc_type: str @return: Tag """ root = Tag('', 1, doc_type) token = re.compile(r'([\+>])?([a-z@\!\#\.][a-z0-9:\-]*)((?:(?:[#\.][\w\-\$]+)|(?:\[[^\]]+\]))+)?(\*(\d*))?(\+$)?', re.IGNORECASE) if not abbr: return None def expando_replace(m): ex = m.group(0) a = get_abbreviation(doc_type, ex) return a and a.value or ex def token_expander(operator, tag_name, attrs, has_multiplier, multiplier, has_expando): multiply_by_lines = (has_multiplier and not multiplier) multiplier = multiplier and int(multiplier) or 1 tag_ch = tag_name[0] if tag_ch == '#' or tag_ch == '.': if attrs: attrs = tag_name + attrs else: attrs = tag_name tag_name = default_tag if has_expando: tag_name += '+' current = is_snippet(tag_name, doc_type) and Snippet(tag_name, multiplier, doc_type) or Tag(tag_name, multiplier, doc_type) if attrs: attrs = parse_attributes(attrs) for attr in attrs: current.add_attribute(attr['name'], attr['value']) # dive into tree if operator == '>' and token_expander.last: token_expander.parent = token_expander.last; token_expander.parent.add_child(current) token_expander.last = current if multiply_by_lines: root.multiply_elem = current return '' # replace expandos abbr = re.sub(r'([a-z][a-z0-9]*)\+$', expando_replace, abbr) token_expander.parent = root token_expander.last = None # abbr = re.sub(token, lambda m: token_expander(m.group(1), m.group(2), m.group(3), m.group(4), m.group(5), m.group(6), m.group(7)), abbr) # Issue from Einar Egilsson abbr = token.sub(lambda m: token_expander(m.group(1), m.group(2), m.group(3), m.group(4), m.group(5), m.group(6)), abbr) root.last = token_expander.last # empty 'abbr' variable means that abbreviation was expanded successfully, # non-empty variable means there was a syntax error return not abbr and root or None; def expand_group(group, doc_type, parent): """ Expand single group item @param group: AbbrGroup @param doc_type: str @param parent: Tag """ tree = abbr_to_primary_tree(group.expr, doc_type) last_item = None if tree: for item in tree.children: last_item = item parent.add_child(last_item) else: raise Exception('InvalidGroup') # set repeating element to the topmost node root = parent while root.parent: root = root.parent root.last = tree.last if tree.multiply_elem: root.multiply_elem = tree.multiply_elem # process child groups if group.children: add_point = last_item.find_deepest_child() or last_item for child in group.children: expand_group(child, doc_type, add_point) def replace_unescaped_symbol(text, symbol, replace): """ Replaces unescaped symbols in text. For example, the '$' symbol will be replaced in 'item$count', but not in 'item\$count'. @param text: Original string @type text: str @param symbol: Symbol to replace @type symbol: st @param replace: Symbol replacement @type replace: str, function @return: str """ i = 0 il = len(text) sl = len(symbol) match_count = 0 while i < il: if text[i] == '\\': # escaped symbol, skip next character i += sl + 1 elif text[i:i + sl] == symbol: # have match cur_sl = sl match_count += 1 new_value = replace if callable(new_value): replace_data = replace(text, symbol, i, match_count) if replace_data: cur_sl = len(replace_data[0]) new_value = replace_data[1] else: new_value = False if new_value is False: # skip replacement i += 1 continue text = text[0:i] + new_value + text[i + cur_sl:] # adjust indexes il = len(text) i += len(new_value) else: i += 1 return text def run_action(name, *args, **kwargs): """ Runs Zen Coding action. For list of available actions and their arguments see zen_actions.py file. @param name: Action name @type name: str @param args: Additional arguments. It may be array of arguments or inline arguments. The first argument should be zen_editor instance @type args: list @example zen_coding.run_actions('expand_abbreviation', zen_editor) zen_coding.run_actions('wrap_with_abbreviation', zen_editor, 'div') """ import zen_actions try: if hasattr(zen_actions, name): return getattr(zen_actions, name)(*args, **kwargs) except: return False def expand_abbreviation(abbr, syntax='html', profile_name='plain'): """ Expands abbreviation into a XHTML tag string @type abbr: str @return: str """ tree_root = parse_into_tree(abbr, syntax); if tree_root: tree = rollout_tree(tree_root) apply_filters(tree, syntax, profile_name, tree_root.filters) return replace_variables(tree.to_string()) return '' def extract_abbreviation(text): """ Extracts abbreviations from text stream, starting from the end @type text: str @return: Abbreviation or empty string """ cur_offset = len(text) start_index = -1 brace_count = 0 while True: cur_offset -= 1 if cur_offset < 0: # moved at string start start_index = 0 break ch = text[cur_offset] if ch == ']': brace_count += 1 elif ch == '[': brace_count -= 1 else: if brace_count: # respect all characters inside attribute sets continue if not is_allowed_char(ch) or (ch == '>' and is_ends_with_tag(text[0:cur_offset + 1])): # found stop symbol start_index = cur_offset + 1 break return text[start_index:] if start_index != -1 else '' def parse_into_tree(abbr, doc_type='html'): """ Parses abbreviation into a node set @param abbr: Abbreviation to transform @type abbr: str @param doc_type: Document type (xsl, html), a key of dictionary where to search abbreviation settings @type doc_type: str @return: Tag """ # remove filters from abbreviation filter_list = [] def filter_replace(m): filter_list.append(m.group(1)) return '' re_filter = re.compile(r'\|([\w\|\-]+)$') abbr = re_filter.sub(filter_replace, abbr) # split abbreviation by groups group_root = split_by_groups(abbr) tree_root = Tag('', 1, doc_type) # then recursively expand each group item try: for item in group_root.children: expand_group(item, doc_type, tree_root) except: # there's invalid group, stop parsing return None tree_root.filters = ''.join(filter_list) return tree_root def is_inside_tag(html, cursor_pos): re_tag = re.compile(r'^<\/?\w[\w\:\-]*.*?>') # search left to find opening brace pos = cursor_pos while pos > -1: if html[pos] == '<': break pos -= 1 if pos != -1: m = re_tag.match(html[pos:]); if m and cursor_pos > pos and cursor_pos < pos + len(m.group(0)): return True return False def wrap_with_abbreviation(abbr, text, doc_type='html', profile='plain'): """ Wraps passed text with abbreviation. Text will be placed inside last expanded element @param abbr: Abbreviation @type abbr: str @param text: Text to wrap @type text: str @param doc_type: Document type (html, xml, etc.) @type doc_type: str @param profile: Output profile's name. @type profile: str @return {String} """ tree_root = parse_into_tree(abbr, doc_type) if tree_root: repeat_elem = tree_root.multiply_elem or tree_root.last repeat_elem.set_content(text) repeat_elem.repeat_by_lines = bool(tree_root.multiply_elem) tree = rollout_tree(tree_root) apply_filters(tree, doc_type, profile, tree_root.filters); return replace_variables(tree.to_string()) return None def get_caret_placeholder(): """ Returns caret placeholder @return: str """ if callable(caret_placeholder): return caret_placeholder() else: return caret_placeholder def set_caret_placeholder(value): """ Set caret placeholder: a string (like '|') or function. You may use a function as a placeholder generator. For example, TextMate uses ${0}, ${1}, ..., ${n} natively for quick Tab-switching between them. @param {String|Function} """ global caret_placeholder caret_placeholder = value def apply_filters(tree, syntax, profile, additional_filters=None): """ Applies filters to tree according to syntax @param tree: Tag tree to apply filters to @type tree: ZenNode @param syntax: Syntax name ('html', 'css', etc.) @type syntax: str @param profile: Profile or profile's name @type profile: str, object @param additional_filters: List or pipe-separated string of additional filters to apply @type additional_filters: str, list @return: ZenNode """ _filters = get_resource(syntax, 'filters') or basic_filters if additional_filters: _filters += '|' if isinstance(additional_filters, basestring): _filters += additional_filters else: _filters += '|'.join(additional_filters) if not _filters: # looks like unknown syntax, apply basic filters _filters = basic_filters return run_filters(tree, profile, _filters) def replace_counter(text, value): """ Replaces '$' character in string assuming it might be escaped with '\' @type text: str @type value: str, int @return: str """ symbol = '$' value = str(value) def replace_func(tx, symbol, pos, match_num): if tx[pos + 1] == '{': # it's a variable, skip it return False # replace sequense of $ symbols with padded number j = pos + 1 while tx[j] == '$' and char_at(tx, j + 1) != '{': j += 1 return (tx[pos:j], value.zfill(j - pos)) return replace_unescaped_symbol(text, symbol, replace_func) def get_profile(name): """ Get profile by it's name. If profile wasn't found, returns 'plain' profile """ return profiles[name] if name in profiles else profiles['plain'] def update_settings(settings): globals()['zen_settings'] = settings class Tag(object): def __init__(self, name, count=1, doc_type='html'): """ @param name: Tag name @type name: str @param count: How many times this tag must be outputted @type count: int @param doc_type: Document type (xsl, html) @type doc_type: str """ name = name.lower() abbr = get_abbreviation(doc_type, name) if abbr and abbr.type == stparser.TYPE_REFERENCE: abbr = get_abbreviation(doc_type, abbr.value) self.name = abbr and abbr.value['name'] or name.replace('+', '') self.count = count self.children = [] self.attributes = [] self.multiply_elem = None self.__attr_hash = {} self._abbr = abbr self.__content = '' self.repeat_by_lines = False self._res = zen_settings.has_key(doc_type) and zen_settings[doc_type] or {} self.parent = None # add default attributes if self._abbr and 'attributes' in self._abbr.value: for a in self._abbr.value['attributes']: self.add_attribute(a['name'], a['value']) def add_child(self, tag): """ Add new child @type tag: Tag """ tag.parent = self self.children.append(tag) def add_attribute(self, name, value): """ Add attribute to tag. If the attribute with the same name already exists, it will be overwritten, but if it's name is 'class', it will be merged with the existed one @param name: Attribute nama @type name: str @param value: Attribute value @type value: str """ # the only place in Tag where pipe (caret) character may exist # is the attribute: escape it with internal placeholder value = replace_unescaped_symbol(value, '|', get_caret_placeholder()); if name in self.__attr_hash: # attribue already exists a = self.__attr_hash[name] if name == 'class': # 'class' is a magic attribute if a['value']: value = ' ' + value a['value'] += value else: a['value'] = value else: a = {'name': name, 'value': value} self.__attr_hash[name] = a self.attributes.append(a) def has_tags_in_content(self): """ This function tests if current tags' content contains XHTML tags. This function is mostly used for output formatting """ return self.get_content() and re_tag.search(self.get_content()) def get_content(self): return self.__content def set_content(self, value): self.__content = value def set_content(self, content): #@DuplicatedSignature self.__content = content def get_content(self): #@DuplicatedSignature return self.__content def find_deepest_child(self): """ Search for deepest and latest child of current element. Returns None if there's no children @return Tag or None """ if not self.children: return None deepest_child = self while True: deepest_child = deepest_child.children[-1] if not deepest_child.children: break return deepest_child class Snippet(Tag): def __init__(self, name, count=1, doc_type='html'): super(Snippet, self).__init__(name, count, doc_type) self.value = replace_unescaped_symbol(get_snippet(doc_type, name), '|', get_caret_placeholder()) self.attributes = {'id': get_caret_placeholder(), 'class': get_caret_placeholder()} self._res = zen_settings[doc_type] def is_block(self): return True class ZenNode(object): """ Creates simplified tag from Zen Coding tag """ def __init__(self, tag): """ @type tag: Tag """ self.type = 'snippet' if isinstance(tag, Snippet) else 'tag' self.name = tag.name self.attributes = tag.attributes self.children = []; self.source = tag "Source element from which current tag was created" # relations self.parent = None self.next_sibling = None self.previous_sibling = None # output params self.start = '' self.end = '' self.content = '' self.padding = '' def add_child(self, tag): """ @type tag: ZenNode """ tag.parent = self if self.children: last_child = self.children[-1] tag.previous_sibling = last_child last_child.next_sibling = tag self.children.append(tag) def get_attribute(self, name): """ Get attribute's value. @type name: str @return: None if attribute wasn't found """ name = name.lower() for attr in self.attributes: if attr['name'].lower() == name: return attr['value'] return None def is_unary(self): """ Test if current tag is unary (no closing tag) @return: bool """ if self.type == 'snippet': return False return (self.source._abbr and self.source._abbr.value['is_empty']) or (self.name in get_elements_collection(self.source._res, 'empty')) def is_inline(self): """ Test if current tag is inline-level (like , ) @return: bool """ return self.name in get_elements_collection(self.source._res, 'inline_level') def is_block(self): """ Test if current element is block-level @return: bool """ return self.type == 'snippet' or not self.is_inline() def has_tags_in_content(self): """ This function tests if current tags' content contains xHTML tags. This function is mostly used for output formatting """ return self.content and re_tag.search(self.content) def has_children(self): """ Check if tag has child elements @return: bool """ return bool(self.children) def has_block_children(self): """ Test if current tag contains block-level children @return: bool """ if self.has_tags_in_content() and self.is_block(): return True for item in self.children: if item.is_block(): return True return False def find_deepest_child(self): """ Search for deepest and latest child of current element Returns None if there's no children @return: ZenNode|None """ if not self.children: return None deepest_child = self while True: deepest_child = deepest_child.children[-1] if not deepest_child.children: break return deepest_child def to_string(self): "@return {String}" content = ''.join([item.to_string() for item in self.children]) return self.start + self.content + content + self.end # create default profiles setup_profile('xhtml'); setup_profile('html', {'self_closing_tag': False}); setup_profile('xml', {'self_closing_tag': True, 'tag_nl': True}); setup_profile('plain', {'tag_nl': False, 'indent': False, 'place_cursor': False}); # This method call explicity loads default settings from zen_settings.py on start up # Comment this line if you want to load data from other resources (like editor's # native snippet) update_settings(stparser.get_settings())