def test_shortcut_id(self):
   line = '#myid'
   node = Parser.parse_line(line)
   self.assertIsInstance(node, nodes.HtmlNode)
   self.assertFalse(node.has_children())
   self.assertEqual('div', node.tag)
   self.assertEqual({'id': 'myid'}, node.attributes)
 def test_jinja_line(self):
   line = '-extends "base.haml"'
   node = Parser.parse_line(line)
   self.assertIsInstance(node, nodes.JinjaNode)
   self.assertEqual('extends', node.tag)
   self.assertEqual('"base.haml"', node.data)
   self.assertFalse(node.has_children())
 def test_single_html_tag(self):
   line = '%div'
   node = Parser.parse_line(line)
   self.assertFalse(node.has_children())
   self.assertIsInstance(node, nodes.HtmlNode)
   self.assertEqual('div', node.tag)
   self.assertEqual({}, node.attributes)
 def test_shortcut_class(self):
   line = '.cls'
   node = Parser.parse_line(line)
   self.assertIsInstance(node, nodes.HtmlNode)
   self.assertFalse(node.has_children())
   self.assertEqual('div', node.tag)
   self.assertEqual({'class': 'cls'}, node.attributes)
 def test_jinja_line(self):
     line = '-extends "base.haml"'
     node = Parser.parse_line(line)
     self.assertIsInstance(node, nodes.JinjaNode)
     self.assertEqual('extends', node.tag)
     self.assertEqual('"base.haml"', node.data)
     self.assertFalse(node.has_children())
 def test_shortcut_id(self):
     line = '#myid'
     node = Parser.parse_line(line)
     self.assertIsInstance(node, nodes.HtmlNode)
     self.assertFalse(node.has_children())
     self.assertEqual('div', node.tag)
     self.assertEqual({'id': 'myid'}, node.attributes)
 def test_shortcut_class(self):
     line = '.cls'
     node = Parser.parse_line(line)
     self.assertIsInstance(node, nodes.HtmlNode)
     self.assertFalse(node.has_children())
     self.assertEqual('div', node.tag)
     self.assertEqual({'class': 'cls'}, node.attributes)
 def test_single_html_tag(self):
     line = '%div'
     node = Parser.parse_line(line)
     self.assertFalse(node.has_children())
     self.assertIsInstance(node, nodes.HtmlNode)
     self.assertEqual('div', node.tag)
     self.assertEqual({}, node.attributes)
예제 #9
0
  def from_haml(cls, haml):
    """Given a line of HAML markup, return the correct HtmlNode."""

    # You can omit the % if and only if the line starts with a '.' or '#'.
    if haml and not haml.startswith('%') and haml[0] in ('.', '#'):
      haml = '%div' + haml

    match = cls.TAG_REGEX.match(haml)
    if not match:
      raise ValueError('Text did not match %s' % cls.TAG_REGEX.pattern)

    # Create the node with the proper tag.
    tag = match.group('tag')

    if tag in cls.SELF_CLOSING_TAGS:
      node = SelfClosingHtmlNode(tag=tag)
    else:
      node = cls(tag=tag)

    # Update the condensed property on the node.
    node.condensed = (match.group('condensed') == '-')

    # Handle shortcut attributes ('.cls#id')
    shortcut_attrs = match.group('shortcut_attrs')
    if shortcut_attrs:
      # Splits into ['.', 'cls', '#', 'id']
      parts = re.split(r'([\.#])', shortcut_attrs)[1:]
      # Zips together into [('.', 'cls'), ('#', 'id')]
      parts = zip(parts[0::2], parts[1::2])

      for prefix, value in parts:
        if prefix == '.':
          node.add_attribute('class', value)
        elif prefix == '#':
          node.add_attribute('id', value)

    # Handle regular attributes ('(a="1", b="2")')
    attrs = match.group('attrs')
    if attrs:
      # Splits apart by commas, but not commas within quotes.
      attr_pairs = re.compile(r'(?:[^,"]|"[^"]*")+').findall(attrs[1:-1])
      if any(_.count('"') != 2 for _ in attr_pairs):
        raise ValueError('Mismatched quotes (or missing comma) in attributes!')

      # Breaks pair strings into [(key, value), ...] but only split apart by
      # the first equal sign.
      attr_pairs = [pair.strip().split('=', 1) for pair in attr_pairs]
      for (key, value) in attr_pairs:
        if not value.startswith('"') or not value.endswith('"'):
          raise ValueError(
              'Invalid attribute provided: "%s" for key "%s"' % (value, key))
        node.add_attribute(key, value[1:-1])

    # Handle in-line content.
    nested = (match.group('nested') or False)
    content = (match.group('content') or '').strip()

    # If we have nested tags, there should definitely be content.
    if nested and not content:
      raise ValueError('Illegal nesting of tags.')

    # If we are nesting tags, parse the content as a separate HAML line and
    # append the parsed child to the current node.
    if nested:
      from pyhaml_jinja.parser import Parser
      child = Parser.parse_line(content.strip())
      node.add_child(child)

    # If we aren't nesting and just have content, append it to the node if
    # possible.
    elif content:
      if not node.children_allowed():
        raise ValueError('Inline content ("%s") not permitted on node %s' % (
          content, node))
      node.add_child(TextNode(content))

    return node
예제 #10
0
 def test_empty_line(self):
   line = ''
   node = Parser.parse_line(line)
   self.assertIsInstance(node, nodes.EmptyNode)
예제 #11
0
 def test_text(self):
   line = 'this is some text'
   node = Parser.parse_line(line)
   self.assertIsInstance(node, nodes.TextNode)
   self.assertEqual('this is some text', node.data)
예제 #12
0
    def from_haml(cls, haml):
        """Given a line of HAML markup parse it into a Jinja node."""

        match = cls.TAG_REGEX.match(haml)
        if not match:
            raise ValueError('Text did not match %s' % cls.TAG_REGEX.pattern)

        # Create the node with the proper tag.
        tag = match.group('tag')
        if tag in cls.SELF_CLOSING_TAGS:
            node = SelfClosingJinjaNode(tag=tag)
        else:
            node = cls(tag=tag)

        data = (match.group('data') or '').strip()

        # To handle nested expressions, we need to be sure that colons are only
        # split apart when they aren't inside brackets, braces, or quotes (both
        # single and double).
        # To do this, we'll just go through "data" and keep a stack of where we
        # are. The first colon we find that is outside the stack is a splitter.
        stack = []
        chars = {
            '[': ']',
            '(': ')',
            '{': '}',
            '\'': '\'',
            '"': '"',
        }

        for index, char in enumerate(data):
            # If we found a colon and the stack is empty, treat it as the splitter
            # for a nested tag. Split, re-parse the rest, attach as a child, and
            # update data to what it should be. Then break out of the loop.
            if not stack and char == ':':
                from pyhaml_jinja.parser import Parser
                child = Parser.parse_line(data[index + 1:].strip())
                node.add_child(child)
                data = data[:index]
                break

            # If we might be able to close an open tag.
            if char in chars.values():
                # The stack is not empty and this tag closes it.
                if stack and char == stack[-1]:
                    stack.pop()
                    continue

                # The stack is not empty, but this tag didn't close it, AND it's not a
                # valid opener. Throw an error.
                elif stack and char not in chars.keys():
                    raise ValueError('Found unexpected closing tag "%s".' %
                                     char)

            # If we're opening, add the proper closing tag to the stack.
            if char in chars.keys():
                stack.append(chars.get(char))

            # TODO: Figure out what we do with backslash escaping characters.

        node.data = data
        return node
 def test_empty_line(self):
     line = ''
     node = Parser.parse_line(line)
     self.assertIsInstance(node, nodes.EmptyNode)
 def test_text(self):
     line = 'this is some text'
     node = Parser.parse_line(line)
     self.assertIsInstance(node, nodes.TextNode)
     self.assertEqual('this is some text', node.data)
예제 #15
0
    def from_haml(cls, haml):
        """Given a line of HAML markup, return the correct HtmlNode."""

        # You can omit the % if and only if the line starts with a '.' or '#'.
        if haml and not haml.startswith('%') and haml[0] in ('.', '#'):
            haml = '%div' + haml

        match = cls.TAG_REGEX.match(haml)
        if not match:
            raise ValueError('Text did not match %s' % cls.TAG_REGEX.pattern)

        # Create the node with the proper tag.
        tag = match.group('tag')

        if tag in cls.SELF_CLOSING_TAGS:
            node = SelfClosingHtmlNode(tag=tag)
        else:
            node = cls(tag=tag)

        # Update the condensed property on the node.
        node.condensed = (match.group('condensed') == '-')

        # Handle shortcut attributes ('.cls#id')
        shortcut_attrs = match.group('shortcut_attrs')
        if shortcut_attrs:
            # Splits into ['.', 'cls', '#', 'id']
            parts = re.split(r'([\.#])', shortcut_attrs)[1:]
            # Zips together into [('.', 'cls'), ('#', 'id')]
            parts = zip(parts[0::2], parts[1::2])

            for prefix, value in parts:
                if prefix == '.':
                    node.add_attribute('class', value)
                elif prefix == '#':
                    node.add_attribute('id', value)

        # Handle regular attributes ('(a="1", b="2")')
        attrs = match.group('attrs')
        if attrs:
            # Splits apart by commas, but not commas within quotes.
            attr_pairs = re.compile(r'(?:[^,"]|"[^"]*")+').findall(attrs[1:-1])
            if any(_.count('"') != 2 for _ in attr_pairs):
                raise ValueError(
                    'Mismatched quotes (or missing comma) in attributes!')

            # Breaks pair strings into [(key, value), ...] but only split apart by
            # the first equal sign.
            attr_pairs = [pair.strip().split('=', 1) for pair in attr_pairs]
            for (key, value) in attr_pairs:
                if not value.startswith('"') or not value.endswith('"'):
                    raise ValueError(
                        'Invalid attribute provided: "%s" for key "%s"' %
                        (value, key))
                node.add_attribute(key, value[1:-1])

        # Handle in-line content.
        nested = (match.group('nested') or False)
        content = (match.group('content') or '').strip()

        # If we have nested tags, there should definitely be content.
        if nested and not content:
            raise ValueError('Illegal nesting of tags.')

        # If we are nesting tags, parse the content as a separate HAML line and
        # append the parsed child to the current node.
        if nested:
            from pyhaml_jinja.parser import Parser
            child = Parser.parse_line(content.strip())
            node.add_child(child)

        # If we aren't nesting and just have content, append it to the node if
        # possible.
        elif content:
            if not node.children_allowed():
                raise ValueError(
                    'Inline content ("%s") not permitted on node %s' %
                    (content, node))
            node.add_child(TextNode(content))

        return node
예제 #16
0
  def from_haml(cls, haml):
    """Given a line of HAML markup parse it into a Jinja node."""

    match = cls.TAG_REGEX.match(haml)
    if not match:
      raise ValueError('Text did not match %s' % cls.TAG_REGEX.pattern)

    # Create the node with the proper tag.
    tag = match.group('tag')
    if tag in cls.SELF_CLOSING_TAGS:
      node = SelfClosingJinjaNode(tag=tag)
    else:
      node = cls(tag=tag)

    data = (match.group('data') or '').strip()

    # To handle nested expressions, we need to be sure that colons are only
    # split apart when they aren't inside brackets, braces, or quotes (both
    # single and double).
    # To do this, we'll just go through "data" and keep a stack of where we
    # are. The first colon we find that is outside the stack is a splitter.
    stack = []
    chars = {
        '[': ']',
        '(': ')',
        '{': '}',
        '\'': '\'',
        '"': '"',
        }

    for index, char in enumerate(data):
      # If we found a colon and the stack is empty, treat it as the splitter
      # for a nested tag. Split, re-parse the rest, attach as a child, and
      # update data to what it should be. Then break out of the loop.
      if not stack and char == ':':
        from pyhaml_jinja.parser import Parser
        child = Parser.parse_line(data[index+1:].strip())
        node.add_child(child)
        data = data[:index]
        break

      # If we might be able to close an open tag.
      if char in chars.values():
        # The stack is not empty and this tag closes it.
        if stack and char == stack[-1]:
          stack.pop()
          continue

        # The stack is not empty, but this tag didn't close it, AND it's not a
        # valid opener. Throw an error.
        elif stack and char not in chars.keys():
          raise ValueError('Found unexpected closing tag "%s".' % char)

      # If we're opening, add the proper closing tag to the stack.
      if char in chars.keys():
        stack.append(chars.get(char))

      # TODO: Figure out what we do with backslash escaping characters.

    node.data = data
    return node