#!/usr/bin/perl -w # Copyright (C) 2005, 2006, 2007, 2009 Apple Inc. All rights reserved. # Copyright (C) 2009, Julien Chaffraix # Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use strict; use Config; use Getopt::Long; use File::Path; use IO::File; use InFilesParser; use Switch; my $printFactory = 0; my $printWrapperFactory = 0; my $tagsFile = ""; my $attrsFile = ""; my $outputDir = "."; my %tags = (); my %attrs = (); my %parameters = (); my $extraDefines = 0; my $preprocessor = "/usr/bin/gcc -E -P -x c++"; GetOptions('tags=s' => \$tagsFile, 'attrs=s' => \$attrsFile, 'factory' => \$printFactory, 'outputDir=s' => \$outputDir, 'extraDefines=s' => \$extraDefines, 'preprocessor=s' => \$preprocessor, 'wrapperFactory' => \$printWrapperFactory); die "You must specify at least one of --tags or --attrs " unless (length($tagsFile) || length($attrsFile)); readNames($tagsFile, "tags") if length($tagsFile); readNames($attrsFile, "attrs") if length($attrsFile); die "You must specify a namespace (e.g. SVG) for Names.h" unless $parameters{'namespace'}; die "You must specify a namespaceURI (e.g. http://www.w3.org/2000/svg)" unless $parameters{'namespaceURI'}; $parameters{'namespacePrefix'} = $parameters{'namespace'} unless $parameters{'namespacePrefix'}; mkpath($outputDir); my $namesBasePath = "$outputDir/$parameters{'namespace'}Names"; my $factoryBasePath = "$outputDir/$parameters{'namespace'}ElementFactory"; my $wrapperFactoryBasePath = "$outputDir/JS$parameters{'namespace'}ElementWrapperFactory"; printNamesHeaderFile("$namesBasePath.h"); printNamesCppFile("$namesBasePath.cpp"); if ($printFactory) { printFactoryCppFile("$factoryBasePath.cpp"); printFactoryHeaderFile("$factoryBasePath.h"); } if ($printWrapperFactory) { printWrapperFactoryCppFile("$wrapperFactoryBasePath.cpp"); printWrapperFactoryHeaderFile("$wrapperFactoryBasePath.h"); } ### Hash initialization sub initializeTagPropertyHash { return ('constructorNeedsCreatedByParser' => 0, 'constructorNeedsFormElement' => 0, 'exportString' => 0, 'interfaceName' => defaultInterfaceName($_[0]), # By default, the JSInterfaceName is the same as the interfaceName. 'JSInterfaceName' => defaultInterfaceName($_[0]), 'mapToTagName' => '', 'wrapperOnlyIfMediaIsAvailable' => 0, 'conditional' => 0); } sub initializeAttrPropertyHash { return ('exportString' => 0); } sub initializeParametersHash { return ('namespace' => '', 'namespacePrefix' => '', 'namespaceURI' => '', 'guardFactoryWith' => '', 'tagsNullNamespace' => 0, 'attrsNullNamespace' => 0, 'exportStrings' => 0); } sub defaultInterfaceName { die "No namespace found" if !$parameters{'namespace'}; return $parameters{'namespace'} . upperCaseName($_[0]) . "Element" } ### Parsing handlers sub tagsHandler { my ($tag, $property, $value) = @_; $tag =~ s/-/_/g; # Initialize default properties' values. $tags{$tag} = { initializeTagPropertyHash($tag) } if !defined($tags{$tag}); if ($property) { die "Unknown property $property for tag $tag\n" if !defined($tags{$tag}{$property}); # The code rely on JSInterfaceName deriving from interfaceName to check for custom JSInterfaceName. # So just override JSInterfaceName if it was not already set. if ($property eq "interfaceName" && $tags{$tag}{'JSInterfaceName'} eq $tags{$tag}{'interfaceName'}) { $tags{$tag}{'JSInterfaceName'} = $value; } $tags{$tag}{$property} = $value; } } sub attrsHandler { my ($attr, $property, $value) = @_; $attr =~ s/-/_/g; # Initialize default properties' values. $attrs{$attr} = { initializeAttrPropertyHash($attr) } if !defined($attrs{$attr}); if ($property) { die "Unknown property $property for attribute $attr\n" if !defined($attrs{$attr}{$property}); $attrs{$attr}{$property} = $value; } } sub parametersHandler { my ($parameter, $value) = @_; # Initialize default properties' values. %parameters = initializeParametersHash() if !(keys %parameters); die "Unknown parameter $parameter for tags/attrs\n" if !defined($parameters{$parameter}); $parameters{$parameter} = $value; } ## Support routines sub readNames { my ($namesFile, $type) = @_; my $names = new IO::File; if ($extraDefines eq 0) { open($names, $preprocessor . " " . $namesFile . "|") or die "Failed to open file: $namesFile"; } else { open($names, $preprocessor . " -D" . join(" -D", split(" ", $extraDefines)) . " " . $namesFile . "|") or die "Failed to open file: $namesFile"; } # Store hashes keys count to know if some insertion occured. my $tagsCount = keys %tags; my $attrsCount = keys %attrs; my $InParser = InFilesParser->new(); switch ($type) { case "tags" { $InParser->parse($names, \¶metersHandler, \&tagsHandler); } case "attrs" { $InParser->parse($names, \¶metersHandler, \&attrsHandler); } else { die "Do not know how to parse $type"; } } close($names); die "Failed to read names from file: $namesFile" if ((keys %tags == $tagsCount) && (keys %attrs == $attrsCount)); } sub printMacros { my ($F, $macro, $suffix, $namesRef) = @_; my %names = %$namesRef; for my $name (sort keys %$namesRef) { print F "$macro $name","$suffix;\n"; if ($parameters{'exportStrings'} or $names{$name}{"exportString"}) { print F "extern char $name", "${suffix}String[];\n"; } } } sub usesDefaultWrapper { my $tagName = shift; return $tagName eq $parameters{'namespace'} . "Element"; } # Build a direct mapping from the tags to the Element to create, excluding # Element that have not constructor. sub buildConstructorMap { my %tagConstructorMap = (); for my $tagName (keys %tags) { my $interfaceName = $tags{$tagName}{'interfaceName'}; next if (usesDefaultWrapper($interfaceName)); if ($tags{$tagName}{'mapToTagName'}) { die "Cannot handle multiple mapToTagName for $tagName\n" if $tags{$tags{$tagName}{'mapToTagName'}}{'mapToTagName'}; $interfaceName = $tags{ $tags{$tagName}{'mapToTagName'} }{'interfaceName'}; } # Chop the string to keep the interesting part. $interfaceName =~ s/$parameters{'namespace'}(.*)Element/$1/; $tagConstructorMap{$tagName} = lc($interfaceName); } return %tagConstructorMap; } # Helper method that print the constructor's signature avoiding # unneeded arguments. sub printConstructorSignature { my ($F, $tagName, $constructorName, $constructorTagName) = @_; print F "static PassRefPtr<$parameters{'namespace'}Element> ${constructorName}Constructor(const QualifiedName& $constructorTagName, Document* doc"; if ($parameters{'namespace'} eq "HTML") { print F ", HTMLFormElement*"; if ($tags{$tagName}{'constructorNeedsFormElement'}) { print F " formElement"; } } print F ", bool"; if ($tags{$tagName}{'constructorNeedsCreatedByParser'}) { print F " createdByParser"; } print F ")\n{\n"; } # Helper method to dump the constructor interior and call the # Element constructor with the right arguments. # The variable names should be kept in sync with the previous method. sub printConstructorInterior { my ($F, $tagName, $interfaceName, $constructorTagName) = @_; # Handle media elements. if ($tags{$tagName}{'wrapperOnlyIfMediaIsAvailable'}) { print F <$headerPath"; printLicenseHeader($F); print F "#ifndef DOM_$parameters{'namespace'}NAMES_H\n"; print F "#define DOM_$parameters{'namespace'}NAMES_H\n\n"; print F "#include \"QualifiedName.h\"\n\n"; print F "namespace WebCore {\n\n namespace $parameters{'namespace'}Names {\n\n"; my $lowerNamespace = lc($parameters{'namespacePrefix'}); print F "#ifndef DOM_$parameters{'namespace'}NAMES_HIDE_GLOBALS\n"; print F "// Namespace\n"; print F "extern const WebCore::AtomicString ${lowerNamespace}NamespaceURI;\n\n"; if (keys %tags) { print F "// Tags\n"; printMacros($F, "extern const WebCore::QualifiedName", "Tag", \%tags); } if (keys %attrs) { print F "// Attributes\n"; printMacros($F, "extern const WebCore::QualifiedName", "Attr", \%attrs); } print F "#endif\n\n"; if (keys %tags) { print F "WebCore::QualifiedName** get$parameters{'namespace'}Tags(size_t* size);\n"; } if (keys %attrs) { print F "WebCore::QualifiedName** get$parameters{'namespace'}Attrs(size_t* size);\n"; } print F "\nvoid init();\n\n"; print F "} }\n\n"; print F "#endif\n\n"; close F; } sub printNamesCppFile { my $cppPath = shift; my $F; open F, ">$cppPath"; printLicenseHeader($F); my $lowerNamespace = lc($parameters{'namespacePrefix'}); print F "#include \"config.h\"\n"; print F "#ifdef SKIP_STATIC_CONSTRUCTORS_ON_GCC\n"; print F "#define DOM_$parameters{'namespace'}NAMES_HIDE_GLOBALS 1\n"; print F "#else\n"; print F "#define QNAME_DEFAULT_CONSTRUCTOR 1\n"; print F "#endif\n\n"; print F "#include \"$parameters{'namespace'}Names.h\"\n\n"; print F "#include \"StaticConstructors.h\"\n"; print F "namespace WebCore {\n\n namespace $parameters{'namespace'}Names { using namespace WebCore; DEFINE_GLOBAL(AtomicString, ${lowerNamespace}NamespaceURI, \"$parameters{'namespaceURI'}\") "; if (keys %tags) { print F "// Tags\n"; for my $name (sort keys %tags) { print F "DEFINE_GLOBAL(QualifiedName, ", $name, "Tag, nullAtom, \"$name\", ${lowerNamespace}NamespaceURI);\n"; } print F "\n\nWebCore::QualifiedName** get$parameters{'namespace'}Tags(size_t* size)\n"; print F "{\n static WebCore::QualifiedName* $parameters{'namespace'}Tags[] = {\n"; for my $name (sort keys %tags) { print F " (WebCore::QualifiedName*)&${name}Tag,\n"; } print F " };\n"; print F " *size = ", scalar(keys %tags), ";\n"; print F " return $parameters{'namespace'}Tags;\n"; print F "}\n"; } if (keys %attrs) { print F "\n// Attributes\n"; for my $name (sort keys %attrs) { print F "DEFINE_GLOBAL(QualifiedName, ", $name, "Attr, nullAtom, \"$name\", ${lowerNamespace}NamespaceURI);\n"; } print F "\n\nWebCore::QualifiedName** get$parameters{'namespace'}Attrs(size_t* size)\n"; print F "{\n static WebCore::QualifiedName* $parameters{'namespace'}Attr[] = {\n"; for my $name (sort keys %attrs) { print F " (WebCore::QualifiedName*)&${name}Attr,\n"; } print F " };\n"; print F " *size = ", scalar(keys %attrs), ";\n"; print F " return $parameters{'namespace'}Attr;\n"; print F "}\n"; } if (keys %tags) { printDefinitionStrings($F, \%tags, "tags"); } if (keys %attrs) { printDefinitionStrings($F, \%attrs, "attributes"); } print F "\nvoid init() { static bool initialized = false; if (initialized) return; initialized = true; // Use placement new to initialize the globals. AtomicString::init(); "; print(F " AtomicString ${lowerNamespace}NS(\"$parameters{'namespaceURI'}\");\n\n"); print(F " // Namespace\n"); print(F " new ((void*)&${lowerNamespace}NamespaceURI) AtomicString(${lowerNamespace}NS);\n\n"); if (keys %tags) { my $tagsNamespace = $parameters{'tagsNullNamespace'} ? "nullAtom" : "${lowerNamespace}NS"; printDefinitions($F, \%tags, "tags", $tagsNamespace); } if (keys %attrs) { my $attrsNamespace = $parameters{'attrsNullNamespace'} ? "nullAtom" : "${lowerNamespace}NS"; printDefinitions($F, \%attrs, "attributes", $attrsNamespace); } print F "}\n\n} }\n\n"; close F; } sub printJSElementIncludes { my $F = shift; my %tagsSeen; for my $tagName (sort keys %tags) { my $JSInterfaceName = $tags{$tagName}{"JSInterfaceName"}; next if defined($tagsSeen{$JSInterfaceName}) || usesDefaultJSWrapper($tagName); $tagsSeen{$JSInterfaceName} = 1; print F "#include \"JS${JSInterfaceName}.h\"\n"; } } sub printElementIncludes { my $F = shift; my %tagsSeen; for my $tagName (sort keys %tags) { my $interfaceName = $tags{$tagName}{"interfaceName"}; next if defined($tagsSeen{$interfaceName}); $tagsSeen{$interfaceName} = 1; print F "#include \"${interfaceName}.h\"\n"; } } sub printDefinitionStrings { my ($F, $namesRef, $type) = @_; my $singularType = substr($type, 0, -1); my $shortType = substr($singularType, 0, 4); my $shortCamelType = ucfirst($shortType); print F "\n// " . ucfirst($type) . " as strings\n"; my %names = %$namesRef; for my $name (sort keys %$namesRef) { next if (!$parameters{'exportStrings'} and !$names{$name}{"exportString"}); my $realName = $name; $realName =~ s/_/-/g; print F "char $name","${shortCamelType}String[] = \"$realName\";\n"; } } sub printDefinitions { my ($F, $namesRef, $type, $namespaceURI) = @_; my $singularType = substr($type, 0, -1); my $shortType = substr($singularType, 0, 4); my $shortCamelType = ucfirst($shortType); my $shortUpperType = uc($shortType); print F " // " . ucfirst($type) . "\n"; my %names = %$namesRef; for my $name (sort keys %$namesRef) { next if ($parameters{'exportStrings'} or $names{$name}{"exportString"}); my $realName = $name; $realName =~ s/_/-/g; print F " const char *$name","${shortCamelType}String = \"$realName\";\n"; } print "\n"; for my $name (sort keys %$namesRef) { print F " new ((void*)&$name","${shortCamelType}) QualifiedName(nullAtom, $name","${shortCamelType}String, $namespaceURI);\n"; } } ## ElementFactory routines sub printFactoryCppFile { my $cppPath = shift; my $F; open F, ">$cppPath"; printLicenseHeader($F); print F < namespace WebCore { using namespace $parameters{'namespace'}Names; END ; print F "typedef PassRefPtr<$parameters{'namespace'}Element> (*ConstructorFunction)(const QualifiedName&, Document*"; if ($parameters{'namespace'} eq "HTML") { print F ", HTMLFormElement*"; } print F ", bool createdByParser);\n"; print F < FunctionMap; static FunctionMap* gFunctionMap = 0; END ; my %tagConstructorMap = buildConstructorMap(); printConstructors($F, \%tagConstructorMap); print F "#if $parameters{'guardFactoryWith'}\n" if $parameters{'guardFactoryWith'}; print F <set(tag.localName().impl(), func); } static inline void createFunctionMapIfNecessary() { if (gFunctionMap) return; // Create the table. gFunctionMap = new FunctionMap; // Populate it with constructor functions. END ; printFunctionInits($F, \%tagConstructorMap); print F "}\n"; print F "#endif\n" if $parameters{'guardFactoryWith'}; print F "\nPassRefPtr<$parameters{'namespace'}Element> $parameters{'namespace'}ElementFactory::create$parameters{'namespace'}Element(const QualifiedName& qName, Document* doc"; if ($parameters{"namespace"} eq "HTML") { print F ", HTMLFormElement* formElement"; } print F ", bool createdByParser)\n{\n"; print F "#if $parameters{'guardFactoryWith'}\n" if $parameters{'guardFactoryWith'}; print F <settings(); if (settings && settings->usesDashboardBackwardCompatibilityMode()) return 0; #endif END ; } print F <get(qName.localName().impl()); if (func) END ; if ($parameters{"namespace"} eq "HTML") { print F " return func(qName, doc, formElement, createdByParser);\n"; } else { print F " return func(qName, doc, createdByParser);\n"; } print F " return new $parameters{'namespace'}Element(qName, doc);\n"; if ($parameters{'guardFactoryWith'}) { print F <$headerPath"; printLicenseHeader($F); print F< namespace WebCore { class Element; class Document; class QualifiedName; class AtomicString; } namespace WebCore { class $parameters{'namespace'}Element; END ; if ($parameters{'namespace'} eq "HTML") { print F " class HTMLFormElement;\n"; } print F< createElement(const WebCore::QualifiedName&, WebCore::Document*, bool createdByParser = true); END ; print F " static PassRefPtr<$parameters{'namespace'}Element> create$parameters{'namespace'}Element(const WebCore::QualifiedName&, WebCore::Document*"; if ($parameters{'namespace'} eq "HTML") { print F ", HTMLFormElement* = 0"; } print F ", bool /*createdByParser*/ = true);\n"; printf F< element) { if (!MediaPlayer::isAvailable()) return CREATE_DOM_NODE_WRAPPER(exec, globalObject, $parameters{'namespace'}Element, element.get()); return CREATE_DOM_NODE_WRAPPER(exec, globalObject, ${JSInterfaceName}, element.get()); } END ; } else { print F < element) { return CREATE_DOM_NODE_WRAPPER(exec, globalObject, ${JSInterfaceName}, element.get()); } END ; } if ($conditional) { print F "#endif\n\n"; } } } sub printWrapperFactoryCppFile { my $cppPath = shift; my $F; open F, ">$cppPath"; printLicenseHeader($F); print F "#include \"config.h\"\n\n"; print F "#if $parameters{'guardFactoryWith'}\n\n" if $parameters{'guardFactoryWith'}; print F "#include \"JS$parameters{'namespace'}ElementWrapperFactory.h\"\n"; printJSElementIncludes($F); print F "\n#include \"$parameters{'namespace'}Names.h\"\n\n"; printElementIncludes($F); print F "\n#include \n\n"; print F <); END ; printWrapperFunctions($F); print F < element) { typedef HashMap FunctionMap; DEFINE_STATIC_LOCAL(FunctionMap, map, ()); if (map.isEmpty()) { END ; for my $tag (sort keys %tags) { # Do not add the name to the map if it does not have a JS wrapper constructor or uses the default wrapper. next if usesDefaultJSWrapper($tag, \%tags); my $conditional = $tags{$tag}{"conditional"}; if ($conditional) { my $conditionalString = "ENABLE(" . join(") && ENABLE(", split(/&/, $conditional)) . ")"; print F "#if ${conditionalString}\n"; } my $ucTag = $tags{$tag}{"JSInterfaceName"}; print F " map.set(${tag}Tag.localName().impl(), create${ucTag}Wrapper);\n"; if ($conditional) { print F "#endif\n"; } } print F <localName().impl()); if (createWrapperFunction) return createWrapperFunction(exec, globalObject, element); return CREATE_DOM_NODE_WRAPPER(exec, globalObject, $parameters{'namespace'}Element, element.get()); } } END ; print F "#endif\n" if $parameters{'guardFactoryWith'}; close F; } sub printWrapperFactoryHeaderFile { my $headerPath = shift; my $F; open F, ">$headerPath"; printLicenseHeader($F); print F "#ifndef JS$parameters{'namespace'}ElementWrapperFactory_h\n"; print F "#define JS$parameters{'namespace'}ElementWrapperFactory_h\n\n"; print F "#if $parameters{'guardFactoryWith'}\n" if $parameters{'guardFactoryWith'}; print F < namespace JSC { class ExecState; } namespace WebCore { class JSNode; class JSDOMGlobalObject; class $parameters{'namespace'}Element; JSNode* createJS$parameters{'namespace'}Wrapper(JSC::ExecState*, JSDOMGlobalObject*, PassRefPtr<$parameters{'namespace'}Element>); } END ; print F "#endif // $parameters{'guardFactoryWith'}\n\n" if $parameters{'guardFactoryWith'}; print F "#endif // JS$parameters{'namespace'}ElementWrapperFactory_h\n"; close F; }