# Copyright (c) 2010 Otchy # This source file is subject to the MIT license. # http://www.otchy.net package SSSCoding; # Ver 1.0.0 use strict; %SSSCoding::SINGLES = ( 'base' => 1, 'meta' => 1, 'img' => 1, 'embed' => 1, 'param' => 1, 'area' => 1, 'link' => 1, 'input' => 1, 'br' => 1, 'hr' => 1, ); %SSSCoding::ATTRS = ( 'a' => 'href=""', 'a:link' => 'href="http://"', 'a:mail' => 'href="mailto:"', 'abbr' => 'title=""', 'acronym' => 'title=""', 'base' => 'href=""', 'bdo' => 'dir=""', 'bdo:r' => 'dir="rtl"', 'bdo:l' => 'dir="ltr"', 'link' => 'rel="stylesheet" href=""', 'link:css' => 'rel="stylesheet" type="text/css" href="style.css" media="all"', 'link:print' => 'rel="stylesheet" type="text/css" href="print.css" media="print"', 'link:favicon' => 'rel="shortcut icon" type="image/x-icon" href="favicon.ico"', 'link:touch' => 'rel="apple-touch-icon" href="favicon.png"', 'link:rss' => 'rel="alternate" type="application/rss+xml" title="RSS" href="rss.xml"', 'link:atom' => 'rel="alternate" type="application/atom+xml" title="Atom" href="atom.xml"', 'meta:utf' => 'http-equiv="Content-Type" content="text/html;charset=UTF-8"', 'meta:win' => 'http-equiv="Content-Type" content="text/html;charset=windows-1251"', 'meta:compat' => 'http-equiv="X-UA-Compatible" content="IE=7"', 'style' => 'type="text/css"', 'script' => 'type="text/javascript"', 'script:src' => 'type="text/javascript" src=""', 'img' => 'src="" alt=""', 'iframe' => 'src="" frameborder="0"', 'embed' => 'src="" type=""', 'object' => 'data="" type=""', 'param' => 'name="" value=""', 'map' => 'name=""', 'area' => 'shape="" coords="" href="" alt=""', 'area:d' => 'shape="default" href="" alt=""', 'area:c' => 'shape="circle" coords="" href="" alt=""', 'area:r' => 'shape="rect" coords="" href="" alt=""', 'area:p' => 'shape="poly" coords="" href="" alt=""', 'form' => 'action=""', 'form:get' => 'action="" method="get"', 'form:post' => 'action="" method="post"', 'label' => 'for=""', 'input' => 'type=""', 'input:hidden' => 'type="hidden" name=""', 'input:h' => 'type="hidden" name=""', 'input:text' => 'type="text" name="" id=""', 'input:t' => 'type="text" name="" id=""', 'input:search' => 'type="search" name="" id=""', 'input:email' => 'type="email" name="" id=""', 'input:url' => 'type="url" name="" id=""', 'input:password' => 'type="password" name="" id=""', 'input:p' => 'type="password" name="" id=""', 'input:datetime' => 'type="datetime" name="" id=""', 'input:date' => 'type="date" name="" id=""', 'input:datetime-local' => 'type="datetime-local" name="" id=""', 'input:month' => 'type="month" name="" id=""', 'input:week' => 'type="week" name="" id=""', 'input:time' => 'type="time" name="" id=""', 'input:number' => 'type="number" name="" id=""', 'input:color' => 'type="color" name="" id=""', 'input:checkbox' => 'type="checkbox" name="" id=""', 'input:c' => 'type="checkbox" name="" id=""', 'input:radio' => 'type="radio" name="" id=""', 'input:r' => 'type="radio" name="" id=""', 'input:range' => 'type="range" name="" id=""', 'input:file' => 'type="file" name="" id=""', 'input:f' => 'type="file" name="" id=""', 'input:submit' => 'type="submit" value=""', 'input:s' => 'type="submit" value=""', 'input:image' => 'type="image" src="" alt=""', 'input:i' => 'type="image" src="" alt=""', 'input:reset' => 'type="reset" value=""', 'input:button' => 'type="button" value=""', 'input:b' => 'type="button" value=""', 'select' => 'name="" id=""', 'option' => 'value=""', 'textarea' => 'name="" id="" cols="30" rows="10"', 'menu:context' => 'type="context"', 'menu:c' => 'type="context"', 'menu:toolbar' => 'type="toolbar"', 'menu:t' => 'type="toolbar"', 'video' => 'src=""', 'audio' => 'src=""', 'html:xml' => 'xmlns="http://www.w3.org/1999/xhtml"', ); sub expand { my $input = shift; if (length($input) == 0) { return; } my @tokens = parse($input); my $root = new SSSCoding::Item('ROOT'); my $current = $root; for (my $i=0; $i<@tokens; $i++) { my $token = $tokens[$i]; my $item = new SSSCoding::Item('ELEMENT', $tokens[$i]); $current->addChild($item); my $nextToken = $tokens[$i + 1]; if (isSpecifier($nextToken)) { if ($nextToken eq '>') { $current = $item; } $i++; } } return $root->toString(); } sub parse { my $input = shift; my @chars = split('', $input); my $token = ''; my @tokens = (); foreach my $c (@chars) { if (isSpecifier($c)) { if (length($token) > 0) { push(@tokens, $token); $token = ''; } push(@tokens, $c); } else { $token .= $c; } } if (length($token) > 0) { push(@tokens, $token); } return @tokens; } sub isSpecifier { my $c = shift; return ($c =~ m/[>\+\(\)]/); } sub isSingle { my $name = shift; return $SSSCoding::SINGLES{$name}; } sub getAttrs { my $name = shift; return $SSSCoding::ATTRS{$name}; } package SSSCoding::Item; use strict; sub new { my $class = shift; my $type = shift; my $input = shift; my $name = ''; my $id = ''; my $class = ''; my @classes = (); my $attrs = ''; my $scale = ''; my $mode = 0; # 0:name, 1:id, 2:class, 3:scale my @children = (); foreach my $c (split('', $input)) { if ($c eq '#' || $c eq '.' || $c eq '*') { if ($c eq '#') { $mode = 1; } elsif ($c eq '.') { $mode = 2; } elsif ($c eq '*') { $mode = 3; } if (length($class) > 0) { push(@classes, $class); $class = ''; } } else { if ($mode == 0) { $name .= $c; } elsif ($mode == 1) { $id .= $c; } elsif ($mode == 2) { $class .= $c; } elsif ($mode == 3) { $scale .= $c; } } } if (length($class) > 0) { push(@classes, $class); } $attrs = SSSCoding::getAttrs($name); if (!$attrs) { $attrs = ''; } $name = (split(':', $name))[0]; if (length($name) == 0) { $name = 'div'; } my $self = {type => $type, name => $name, id => $id, classes => \@classes, scale => $scale, attrs => $attrs, children => \@children}; return bless $self, 'SSSCoding::Item'; } sub getType { my $self = shift; return $self->{type}; } sub getName { my $self = shift; return $self->{name}; } sub isSingle { my $self = shift; return SSSCoding::isSingle($self->getName()); } sub setParent { my $self = shift; $self->{parent} = shift; } sub getParent { my $self = shift; return $self->{parent}; } sub getId { my $self = shift; my $count = shift; my $id = $self->{id}; if ($count > 0) { $id =~ s/\$/$count/g; } return $id; } sub hasId { my $self = shift; return length($self->getId()) > 0; } sub getClasses { my $self = shift; return @{$self->{classes}}; } sub getClass { my $self = shift; my $count = shift; my $class = join(' ', $self->getClasses()); if ($count > 0) { $class =~ s/\$/$count/g; } return $class; } sub hasClass { my $self = shift; my $len = $self->getClasses(); return $len > 0; } sub getScale { my $self = shift; return $self->{scale} + 0; } sub hasScale { my $self = shift; return $self->getScale() > 0; } sub addChild { my $self = shift; my $item = shift; push(@{$self->{children}}, $item); } sub getAttrs { my $self = shift; return $self->{attrs}; } sub hasAttrs { my $self = shift; return length($self->{attrs}) > 0; } sub getChild { my $self = shift; my $i = shift; return @{$self->{children}}[$i]; } sub getChildren { my $self = shift; return @{$self->{children}}; } sub toString { my $self = shift; my $str = ''; if ($self->{type} eq 'ELEMENT') { if ($self->hasScale()) { my $scale = $self->getScale(); for (my $i=0; $i<$scale; $i++) { $str .= $self->toStringElement($i+1); } } else { $str .= $self->toStringElement(); } } else { foreach my $child ($self->getChildren()) { $str .= $child->toString(); } } return $str; } sub toStringElement { my $self = shift; my $count = shift; my $str = ''; $str .= '<' . $self->getName(); if ($self->hasId()) { $str .= ' id="' . $self->getId($count) . '"'; } if ($self->hasClass()) { $str .= ' class="' . $self->getClass($count) . '"'; } if ($self->hasAttrs()) { $str .= ' ' . $self->getAttrs(); } if ($self->isSingle()) { $str .= ' />'; return $str; } $str .= '>'; foreach my $child ($self->getChildren()) { $str .= $child->toString(); } $str .= 'getName() . '>'; return $str; } return 1;