text:
title: "Exploring the open source 2048 game javascript examples"
slug: exploring-open-source-2048-game-javascript-examples
published: true
layout: post
type: post
contributors: [sethvincent]
topics: [javascript, games]
---

I spent a lot of time over the past few days playing this game called [2048](http://gabrielecirulli.github.io/2048/), which is a clone of a game called [1024](https://play.google.com/store/apps/details?id=com.veewo.a1024), which appears to be a clone of the game [Threes](http://asherv.com/threes/).

I liked playing this 2048 game. And it took more time and strategy than I expected, but I beat it.

Now I'm seeing new versions pop up, and it's a fun learning exercise to explore the source code of these games.

## 2048

The 2048 game that stole my free time!

Demo: [http://gabrielecirulli.github.io/2048/](http://gabrielecirulli.github.io/2048/)

Source: [https://github.com/gabrielecirulli/2048/](https://github.com/gabrielecirulli/2048/)

## 2048 AI

This version implements an AI.

Demo: [http://ov3y.github.io/2048-AI/](http://ov3y.github.io/2048-AI/)

Source: [https://github.com/ov3y/2048-AI](https://github.com/ov3y/2048-AI)

## Flappy 2048

The 2048 game board repurposed for a Flappy-like.

Demo: [http://hczhcz.github.io/Flappy-2048/](http://hczhcz.github.io/Flappy-2048/)

Source: [https://github.com/hczhcz/Flappy-2048/](https://github.com/hczhcz/Flappy-2048/)

## DOGE2048

Probably pretty much what you would expect.

Demo: [http://doge2048.com/](http://doge2048.com/)

Source: [https://github.com/laferrera/doge2048](https://github.com/laferrera/doge2048)

## Flappy DOGE2048

Also pretty much what you would expect.

Demo: [http://www.donaldguy.com/Flappy-Doge2048/](http://www.donaldguy.com/Flappy-Doge2048/)

Source: [https://github.com/donaldguy/Flappy-Doge2048](https://github.com/donaldguy/Flappy-Doge2048)

## Tumblr 2048

Play 2048, with the tiles replaced by gifs from Tumblr. Fun.

Demo: [http://kevintcoughlin.com/tumblr-2048/](http://kevintcoughlin.com/tumblr-2048/)

Source: [https://github.com/KevinTCoughlin/tumblr-2048](https://github.com/KevinTCoughlin/tumblr-2048)

## 2048-Tetris

It's 2048, but also it's Tetris!

Demo: [http://prat0318.github.io/2048-tetris/](http://prat0318.github.io/2048-tetris/)

Source: [https://github.com/prat0318/2048-tetris/](https://github.com/prat0318/2048-tetris/)

## 2x2048

Two player 2048!

Demo: [http://instapainting.com/2x2048/index.html](http://instapainting.com/2x2048/index.html)

Source: _Not found on GitHub!_ But you can view source on the demo.

## 16384 Hex

I really like this hexagon version of 2048.

Demo: [http://rudradevbasak.github.io/16384_hex/](http://rudradevbasak.github.io/16384_hex/)

Source: [https://github.com/rudradevbasak/16384_hex/](https://github.com/rudradevbasak/16384_hex/)

## 2048 1D
A 1x21 game board. Silly easy.

Demo: [http://tiansh.github.io/2048/](http://tiansh.github.io/2048/)

Source: [https://github.com/tiansh/2048/](https://github.com/tiansh/2048/)

## 2048 3D

A "3d" game board that allows you to push tiles across three quadrants.

Demo: [http://joppi.github.io/2048-3D](http://joppi.github.io/2048-3D)

Source: [https://github.com/joppi/2048-3D](https://github.com/joppi/2048-3D)

## 2048 4D

This fork breaks the game board into 4 quadrants that you can push tiles between. Very challenging!

Demo: [http://huonw.github.io/2048-4D/](http://huonw.github.io/2048-4D/)

Source: [https://github.com/huonw/2048-4D](https://github.com/huonw/2048-4D)

## 2048 - Multiplayer

A multiplayer version! Fun for spying other people's strategies.

Demo: [http://emils.github.io/2048-multiplayer/](http://emils.github.io/2048-multiplayer/)

Source: [https://github.com/emils/2048-multiplayer/](https://github.com/emils/2048-multiplayer/)

## HN Plays 2048

Hundreds of people at once moving tiles around!

Demo: [https://hnplays2048.herokuapp.com/](https://hnplays2048.herokuapp.com/)

Source: [https://github.com/grant/hnplays2048](https://github.com/grant/hnplays2048)

## 2048 in 2048 bytes

A super minimal version submitted to js1k.

Demo: [http://js1k.com/2014-dragons/demo/1813](http://js1k.com/2014-dragons/demo/1813)

Source: [http://js1k.com/2014-dragons/details/1813](http://js1k.com/2014-dragons/details/1813)

## 177147
This one is a simple fork that uses powers of 3 rather than powers of 2. It's very satisfying to see such big numbers.

Demo: [http://inky.github.io/177147/](http://inky.github.io/177147/)

Source: [https://github.com/inky/177147](https://github.com/inky/177147)

## 2048 Numberwang

This one just fucks with the numbers being displayed, but you still match the same colored tiles together.

Demo: [http://louhuang.com/2048-numberwang/](http://louhuang.com/2048-numberwang/)

Source: [https://github.com/louh/2048-numberwang](https://github.com/louh/2048-numberwang)

## Dropbox 2048

Save your 2048 game scores to Dropbox. Nice example of Dropbox integration.

Demo: [https://dropbox2048.site44.com/](https://dropbox2048.site44.com/)

Source: [https://github.com/leah/2048](https://github.com/leah/2048)

## 2584 Fibonacci

2048 with Fibonaccis numbers.

Demo: [http://mike199515.free3v.com/1597/2.htm](http://mike199515.free3v.com/1597/2.htm)

Source: _Not found on GitHub!_ But you can view source on the demo.

## 2048x5

2048 in a 5 by 5 grid.

Demo: [http://2048.kaivoslab.com/](http://2048.kaivoslab.com)

Source: _Not found on GitHub!_ But you can view source on the demo.

# There's more.

2048 has been forked over 1500 times, so there's likely other interesting versions of the game!


text:
const request = require('request');
const git = require('git-rev-sync');
const gutil = require('gulp-util');

module.exports = function notify(project, _org, branch, env, url, cb) {
  // we still support also the legacy flowdock PUSH api
  // notification, which will be delivered if a FLOW_TOKEN
  // is defined.

  if (!process.env.FLOW_TOKEN) {
    cb();
    return;
  }

  const gitMessage = git.message();

  const options = {
    url: `https://api.flowdock.com/v1/messages/team_inbox/${process.env.FLOW_TOKEN}`,
    method: 'POST',
    json: true,
    body: {
      source: 'CircleCI',
      from_address: 'deploy@lucify.com',
      subject: `Deployed branch ${project}/${branch} to ${env}`,
      content: `<p>${gitMessage}</p> <p>${url}</p>`,
      project,
      tags: ['#deployment', `#${env}`],
    },
  };
  request(options, (error, res, body) => {
    if (error) {
      gutil.log(error);
      return cb();
    }
    if (res.statusCode !== 200) {
      gutil.log(`STATUS: ${res.statusCode}`);
      gutil.log(`HEADERS: ${JSON.stringify(res.headers)}`);
      gutil.log(`BODY: ${JSON.stringify(body)}`);
    }
    return cb();
  });
};

redpajama_set_name: RedPajamaGithub


text:
package cloudfoundry_test

import (
	"crypto/tls"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"time"

	"code.cloudfoundry.org/copilot/testhelpers"
	"github.com/onsi/gomega"

	"istio.io/istio/pilot/pkg/serviceregistry/cloudfoundry"
)

type testState struct {
	// generated credentials for server and client
	creds testhelpers.MTLSCredentials

	// path on disk to store the config.yaml
	configFilePath string

	// object under test
	config *cloudfoundry.Config
}

func (s *testState) cleanup() {
	s.creds.CleanupTempFiles()
	_ = os.RemoveAll(s.configFilePath)
}

func newTestState() *testState {
	creds := testhelpers.GenerateMTLS()
	clientTLSFiles := creds.CreateClientTLSFiles()
	return &testState{
		creds:          creds,
		configFilePath: filepath.Join(creds.TempDir, "config.yaml"),
		config: &cloudfoundry.Config{
			Copilot: cloudfoundry.CopilotConfig{
				ServerCACertPath: clientTLSFiles.ServerCA,
				ClientCertPath:   clientTLSFiles.ClientCert,
				ClientKeyPath:    clientTLSFiles.ClientKey,
				Address:          "https://copilot.dns.address:port",
				PollInterval:     90 * time.Second,
			},
			ServicePort: 8080,
		},
	}
}

func TestConfig_SaveAndLoad(t *testing.T) {
	g := gomega.NewGomegaWithT(t)

	state := newTestState()
	defer state.cleanup()

	err := state.config.Save(state.configFilePath)
	g.Expect(err).To(gomega.BeNil())

	loadedConfig, err := cloudfoundry.LoadConfig(state.configFilePath)
	g.Expect(err).To(gomega.BeNil())

	g.Expect(loadedConfig).To(gomega.Equal(state.config))
}

func TestConfig_Load_FileNotFound(t *testing.T) {
	g := gomega.NewGomegaWithT(t)

	state := newTestState()
	defer state.cleanup()

	_, err := cloudfoundry.LoadConfig(state.configFilePath)

	g.Expect(err).NotTo(gomega.BeNil())
	g.Expect(err.Error()).To(gomega.HavePrefix("reading config file: open"))
}

func TestConfig_Load_BadYAML(t *testing.T) {
	g := gomega.NewGomegaWithT(t)

	state := newTestState()
	defer state.cleanup()

	err := ioutil.WriteFile(state.configFilePath, []byte("bad yaml"), 0600)
	g.Expect(err).To(gomega.BeNil())

	_, err = cloudfoundry.LoadConfig(state.configFilePath)
	g.Expect(err.Error()).To(gomega.HavePrefix("parsing config: yaml"))
}

func TestConfig_Load_MissingRequiredFields(t *testing.T) {
	requiredFieldNames := []string{
		"ServerCACertPath",
		"ClientCertPath",
		"ClientKeyPath",
		"Address",
		"PollInterval",
	}
	for _, n := range requiredFieldNames {
		// don't close over the iterator: https://golang.org/doc/faq#closures_and_goroutines
		fieldName := n

		t.Run("required field "+fieldName, func(t *testing.T) {
			g := gomega.NewGomegaWithT(t)

			state := newTestState()
			defer state.cleanup()

			// zero out the named field of the config struct
			fieldValue := reflect.Indirect(reflect.ValueOf(&state.config.Copilot)).FieldByName(fieldName)
			fieldValue.Set(reflect.Zero(fieldValue.Type()))

			// save to the file
			err := state.config.Save(state.configFilePath)
			g.Expect(err).To(gomega.BeNil())

			// attempt to load it
			_, err = cloudfoundry.LoadConfig(state.configFilePath)
			g.Expect(err).NotTo(gomega.BeNil())
			g.Expect(err.Error()).To(gomega.HavePrefix("invalid config: Copilot." + fieldName))
		})
	}
}

func TestConfig_TLSClient_Connects(t *testing.T) {
	g := gomega.NewGomegaWithT(t)

	state := newTestState()
	defer state.cleanup()

	// launch a TLS server
	serverListener, err := tls.Listen("tcp", "127.0.0.1:", state.creds.ServerTLSConfig())
	g.Expect(err).To(gomega.BeNil())

	msgs := make(chan string)
	go func() {
		for {
			conn, err2 := serverListener.Accept()
			if err2 != nil {
				return
			}
			b := make([]byte, 6)
			nBytesRead, _ := conn.Read(b)
			msgs <- string(b[:nBytesRead])
		}
	}()
	defer serverListener.Close()

	// generate client TLS config from the object under test
	clientTLSConfig, err := state.config.ClientTLSConfig()
	g.Expect(err).To(gomega.BeNil())

	clientTLSConfig.ServerName = "127.0.0.1" // a little hack to avoid name mismatch error

	// can use this TLS config to connect to the TLS server
	conn, err := tls.Dial("tcp", serverListener.Addr().String(), clientTLSConfig)
	g.Expect(err).To(gomega.BeNil())
	defer conn.Close()

	// can write some bytes to the server
	_, err = conn.Write([]byte("potato"))
	g.Expect(err).To(gomega.BeNil())

	receivedMsg := <-msgs
	g.Expect(receivedMsg).To(gomega.Equal("potato"))
}

func TestConfig_TLSClient_HasSecureParameters(t *testing.T) {
	g := gomega.NewGomegaWithT(t)

	state := newTestState()
	defer state.cleanup()

	tlsConfig, err := state.config.ClientTLSConfig()
	g.Expect(err).To(gomega.BeNil())

	g.Expect(tlsConfig.MinVersion).To(gomega.Equal(uint16(tls.VersionTLS12)))
	g.Expect(tlsConfig.PreferServerCipherSuites).To(gomega.BeTrue())
	g.Expect(tlsConfig.CipherSuites).To(gomega.ConsistOf([]uint16{
		tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
	}))
	g.Expect(tlsConfig.CurvePreferences).To(gomega.Equal([]tls.CurveID{tls.CurveP384}))

	rawSubjectData := string(tlsConfig.RootCAs.Subjects()[0])
	if !strings.Contains(rawSubjectData, "serverCA") {
		t.Errorf("expected to find substring 'serverCA' in root CAs subject data")
	}
}

func TestConfig_TLSClient_Errors(t *testing.T) {
	// checking that we get useful errors when the config is broken in various ways
	breakageTypes := map[string]func(path string){
		"remove":       func(path string) { _ = os.Remove(path) },
		"writeBadData": func(path string) { _ = ioutil.WriteFile(path, []byte("bad-data"), 0600) },
	}

	testCases := []struct {
		field             string
		breakage          string
		expectedErrPrefix string
	}{
		{
			"ServerCACertPath",
			"remove",
			"loading server CAs: open",
		},
		{
			"ServerCACertPath",
			"writeBadData",
			"loading server CAs: failed to find any PEM data",
		},
		{
			"ClientCertPath",
			"remove",
			"loading client cert/key: open",
		},
		{
			"ClientCertPath",
			"writeBadData",
			"loading client cert/key: tls: failed to find any PEM data",
		},
		{
			"ClientKeyPath",
			"remove",
			"loading client cert/key: open",
		},
		{
			"ClientKeyPath",
			"writeBadData",
			"loading client cert/key: tls: failed to find any PEM data",
		},
	}

	for _, tc := range testCases {
		// don't close over the iterator: https://golang.org/doc/faq#closures_and_goroutines
		testCase := tc

		t.Run(testCase.field+" "+testCase.breakage, func(t *testing.T) {
			g := gomega.NewGomegaWithT(t)

			state := newTestState()
			defer state.cleanup()

			// the named field of the config struct
			fieldValue := reflect.Indirect(reflect.ValueOf(&state.config.Copilot)).FieldByName(testCase.field)
			path := fieldValue.String()

			// mess up the file
			breakageTypes[testCase.breakage](path)

			// attempt to construct the client config
			_, err := state.config.ClientTLSConfig()

			g.Expect(err).To(gomega.MatchError(gomega.HavePrefix(testCase.expectedErrPrefix)))
		})
	}
}


text:
This section introduces the dsl and conf for usage of different type of task.

#### Fit Task.

    "iv-top-k": {
        "conf": "hetero_feature_selection_iv_top_k_conf.json",
        "dsl": "hetero_feature_selection_iv_top_k_dsl.json"
    },
    "iv-selection": {
        "conf": "hetero_feature_selection_iv_selection_conf.json",
        "dsl": "hetero_feature_selection_iv_selection_dsl.json"
    },
    "multi-host": {
        "conf": "hetero_feature_selection_multi_host_conf.json",
        "dsl": "hetero_feature_selection_multi_host_dsl.json"
    },
    "selection": {
        "conf": "hetero_feature_selection_selection_conf.json",
        "dsl": "hetero_feature_selection_selection_dsl.json"
    },
    "manually": {
        "conf": "hetero_feature_selection_manually_conf.json",
        "dsl": "hetero_feature_selection_manually_dsl.json"
    },
    "select-cols": {
        "conf": "hetero_feature_selection_select_cols_conf.json",
        "dsl": "hetero_feature_selection_select_cols_dsl.json"
    },
    "select-col-names": {
        "conf": "hetero_feature_selection_select_col_names_conf.json",
        "dsl": "hetero_feature_selection_select_col_names_dsl.json"
    },
    "percentage-value": {
        "conf": "hetero_feature_selection_percentage_value_conf.json",
        "dsl": "hetero_feature_selection_percentage_value_dsl.json"
    },
    "manually-left": {
        "conf": "hetero_feature_selection_manually_left_conf.json",
        "dsl": "hetero_feature_selection_manually_left_dsl.json"
    },
    "multi-iso": {
        "conf": "hetero_feature_selection_multi_iso_conf.json",
        "dsl": "hetero_feature_selection_multi_iso_dsl.json"
    },
    "fast-sbt": {
        "conf": "hetero_feature_selection_fast_sbt_conf.json",
        "dsl": "hetero_feature_selection_fast_sbt_dsl.json"
    },
    "single-predict": {
        "conf": "hetero_feature_selection_single_predict_conf.json",
        "dsl": "hetero_feature_selection_single_predict_dsl.json"
    },
    "hetero_feature_selection_select_anonymous_col_names": {
            "conf": "hetero_feature_selection_select_anonymous_col_names_conf.json",
            "dsl": "hetero_feature_selection_select_anonymous_col_names_dsl.json"
    },
    "hetero_feature_selection_manually_anonymous": {
            "conf": "hetero_feature_selection_manually_anonymous_conf.json",
            "dsl": "hetero_feature_selection_manually_anonymous_dsl.json"
    },
    "hetero_feature_selection_manually_left_anonymous": {
            "conf": "hetero_feature_selection_manually_left_anonymous_conf.json",
            "dsl": "hetero_feature_selection_manually_left_anonymous_dsl.json"
    }

Users can use following commands to running the task.

    flow job submit -c ${runtime_config} -d ${dsl}

Moreover, after successfully running the fitting task, you can use it to transform too.

For selection using anonymous feature name cases, make sure to change party id value in host feature names accordingly.
Alternatively, try pipeline examples, where party id will be automatically replaced.


text:
from libsaas.services import base

from . import resource, categories, streams, suggestions

class ForumsBase(resource.UserVoiceResource):

    path = 'forums'

    def wrap_object(self, obj):
        return {'forum': obj}

class Forums(ForumsBase):
    pass

class Forum(ForumsBase):

    @base.resource(suggestions.ForumUserSuggestions)
    def user_suggestions(self, user_id):
        """
        Return a resource corresponding to all the suggestions of a  single
        user on this forum.
        """
        return suggestions.ForumUserSuggestions(self, user_id)

    @base.resource(suggestions.ForumSuggestion)
    def suggestion(self, suggestion_id):
        """
        Return a resource corresponding to a single suggestion on a forum.
        """
        return suggestions.ForumSuggestion(self, suggestion_id)

    @base.resource(suggestions.ForumSuggestions)
    def suggestions(self):
        """
        Return a resource corresponding to all the suggestion on a forum.
        """
        return suggestions.ForumSuggestions(self)

    @base.resource(streams.Stream)
    def stream(self):
        """
        Return a resource corresponding to the stream of events for this forum.
        """
        return streams.Stream(self)

    @base.resource(categories.ForumCategory)
    def category(self, category_id):
        """
        Return a resource corresponding to a single category on this forum.
        """
        return categories.ForumCategory(self, category_id)

    @base.resource(categories.ForumCategories)
    def categories(self):
        """
        Return a resource corresponding to all the categories on this forum.
        """
        return categories.ForumCategories(self)


text:
require 'spidr/sanitizers'
require 'spidr/filters'
require 'spidr/events'
require 'spidr/actions'
require 'spidr/page'
require 'spidr/session_cache'
require 'spidr/cookie_jar'
require 'spidr/auth_store'
require 'spidr/spidr'

require 'openssl'
require 'net/http'
require 'set'

module Spidr
  class Agent

    include Sanitizers
    include Filters
    include Events
    include Actions

    # HTTP Host Header to use
    #
    # @return [String]
    attr_accessor :host_header

    # HTTP Host Headers to use for specific hosts
    #
    # @return [Hash{String,Regexp => String}]
    attr_reader :host_headers

    # User-Agent to use
    #
    # @return [String]
    attr_accessor :user_agent

    # HTTP Authentication credentials
    #
    # @return [AuthStore]
    attr_accessor :authorized

    # Referer to use
    #
    # @return [String]
    attr_accessor :referer

    # Delay in between fetching pages
    #
    # @return [Integer]
    attr_accessor :delay

    # History containing visited URLs
    #
    # @return [Set<URI::HTTP>]
    attr_reader :history

    # List of unreachable URLs
    #
    # @return [Set<URI::HTTP>]
    attr_reader :failures

    # Queue of URLs to visit
    #
    # @return [Array<URI::HTTP>]
    attr_reader :queue

    # Cached cookies
    #
    # @return [CookieJar]
    attr_reader :cookies

    # Maximum depth
    #
    # @return [Integer]
    attr_reader :max_depth

    # The visited URLs and their depth within a site
    #
    # @return [Hash{URI::HTTP => Integer}]
    attr_reader :levels

    #
    # Creates a new Agent object.
    #
    # @param [Hash] options
    #   Additional options
    #
    # @option options [Hash] :proxy (Spidr.proxy)
    #   The proxy information to use.
    #
    # @option :proxy [String] :host
    #   The host the proxy is running on.
    #
    # @option :proxy [Integer] :port
    #   The port the proxy is running on.
    #
    # @option :proxy [String] :user
    #   The user to authenticate as with the proxy.
    #
    # @option :proxy [String] :password
    #   The password to authenticate with.
    #
    # @option options [String] :host_header
    #   The HTTP Host header to use with each request.
    #
    # @option options [Hash{String,Regexp => String}] :host_headers
    #   The HTTP Host headers to use for specific hosts.
    #
    # @option options [String] :user_agent (Spidr.user_agent)
    #   The User-Agent string to send with each requests.
    #
    # @option options [String] :referer
    #   The Referer URL to send with each request.
    #
    # @option options [Integer] :delay (0)
    #   The number of seconds to pause between each request.
    #
    # @option options [Set, Array] :queue
    #   The initial queue of URLs to visit.
    #
    # @option options [Set, Array] :history
    #   The initial list of visited URLs.
    #
    # @option options [Integer] :max_depth
    #   The maximum link depth to follow.
    #
    # @yield [agent]
    #   If a block is given, it will be passed the newly created agent
    #   for further configuration.
    #
    # @yieldparam [Agent] agent
    #   The newly created agent.
    #
    # @see #initialize_sanitizers
    # @see #initialize_filters
    # @see #initialize_actions
    # @see #initialize_events
    #
    def initialize(options={})
      @host_header  = options[:host_header]
      @host_headers = {}

      if options[:host_headers]
        @host_headers.merge!(options[:host_headers])
      end

      @user_agent = options.fetch(:user_agent,Spidr.user_agent)
      @referer    = options[:referer]

      @sessions   = SessionCache.new(options.fetch(:proxy,Spidr.proxy))
      @cookies    = CookieJar.new
      @authorized = AuthStore.new

      @running  = false
      @delay    = options.fetch(:delay,0)
      @history  = Set[]
      @failures = Set[]
      @queue    = []

      @levels    = Hash.new(0)
      @max_depth = options[:max_depth]

      initialize_sanitizers(options)
      initialize_filters(options)
      initialize_actions(options)
      initialize_events(options)

      yield self if block_given?
    end

    #
    # Creates a new agent and begin spidering at the given URL.
    #
    # @param [URI::HTTP, String] url
    #   The URL to start spidering at.
    #
    # @param [Hash] options
    #   Additional options. See {Agent#initialize}.
    #
    # @yield [agent]
    #   If a block is given, it will be passed the newly created agent
    #   before it begins spidering.
    #
    # @yieldparam [Agent] agent
    #   The newly created agent.
    #
    # @see #initialize
    # @see #start_at
    #
    def self.start_at(url,options={},&block)
      agent = new(options,&block)
      agent.start_at(url)
    end

    #
    # Creates a new agent and spiders the web-site located at the given URL.
    #
    # @param [URI::HTTP, String] url
    #   The web-site to spider.
    #
    # @param [Hash] options
    #   Additional options. See {Agent#initialize}.
    #
    # @yield [agent]
    #   If a block is given, it will be passed the newly created agent
    #   before it begins spidering.
    #
    # @yieldparam [Agent] agent
    #   The newly created agent.
    #
    # @see #initialize
    #
    def self.site(url,options={},&block)
      url = URI(url.to_s) unless url.kind_of?(URI)

      agent = new(options.merge(:host => url.host),&block)
      agent.start_at(url)
    end

    #
    # Creates a new agent and spiders the given host.
    #
    # @param [String] name
    #   The host-name to spider.
    #
    # @param [Hash] options
    #   Additional options. See {Agent#initialize}.
    #
    # @yield [agent]
    #   If a block is given, it will be passed the newly created agent
    #   before it begins spidering.
    #
    # @yieldparam [Agent] agent
    #   The newly created agent.
    #
    # @see #initialize
    #
    def self.host(name,options={},&block)
      agent = new(options.merge(:host => name),&block)
      agent.start_at(URI::HTTP.build(:host => name, :path => '/'))
    end

    #
    # Clears the history of the agent.
    #
    def clear
      @queue.clear
      @history.clear
      @failures.clear
      return self
    end

    #
    # Start spidering at a given URL.
    #
    # @param [URI::HTTP, String] url
    #   The URL to start spidering at.
    #
    # @yield [page]
    #   If a block is given, it will be passed every page visited.
    #
    # @yieldparam [Page] page
    #   A page which has been visited.
    #
    def start_at(url,&block)
      enqueue(url)
      return run(&block)
    end

    #
    # Start spidering until the queue becomes empty or the agent is
    # paused.
    #
    # @yield [page]
    #   If a block is given, it will be passed every page visited.
    #
    # @yieldparam [Page] page
    #   A page which has been visited.
    #
    def run(&block)
      @running = true

      until (@queue.empty? || paused?)
        begin
          visit_page(dequeue,&block)
        rescue Actions::Paused
          return self
        rescue Actions::Action
        end
      end

      @running = false
      @sessions.clear
      return self
    end

    #
    # Determines if the agent is running.
    #
    # @return [Boolean]
    #   Specifies whether the agent is running or stopped.
    #
    def running?
      @running == true
    end

    #
    # The proxy information the agent uses.
    #
    # @return [Hash]
    #   The proxy information.
    #
    # @see SessionCache#proxy
    #
    # @since 0.2.2
    #
    def proxy
      @sessions.proxy
    end

    #
    # Sets the proxy information that the agent uses.
    #
    # @param [Hash] new_proxy
    #   The new proxy information.
    #
    # @return [Hash]
    #   The new proxy information.
    #
    # @see SessionCache#proxy=
    #
    # @since 0.2.2
    #
    def proxy=(new_proxy)
      @sessions.proxy = new_proxy
    end

    #
    # Sets the history of URLs that were previously visited.
    #
    # @param [#each] new_history
    #   A list of URLs to populate the history with.
    #
    # @return [Set<URI::HTTP>]
    #   The history of the agent.
    #
    # @example
    #   agent.history = ['http://tenderlovemaking.com/2009/05/06/ann-nokogiri-130rc1-has-been-released/']
    #
    def history=(new_history)
      @history.clear

      new_history.each do |url|
        url = URI(url.to_s) unless url.kind_of?(URI)

        @history << url
      end

      return @history
    end

    alias visited_urls history

    #
    # Specifies the links which have been visited.
    #
    # @return [Array<String>]
    #   The links which have been visited.
    #
    def visited_links
      @history.map { |url| url.to_s }
    end

    #
    # Specifies all hosts that were visited.
    #
    # @return [Array<String>]
    #   The hosts which have been visited.
    #
    def visited_hosts
      visited_urls.map { |uri| uri.host }.uniq
    end

    #
    # Determines whether a URL was visited or not.
    #
    # @param [URI::HTTP, String] url
    #   The URL to search for.
    #
    # @return [Boolean]
    #   Specifies whether a URL was visited.
    #
    def visited?(url)
      url = URI(url.to_s) unless url.kind_of?(URI)

      return @history.include?(url)
    end

    #
    # Sets the list of failed URLs.
    #
    # @param [#each] new_failures
    #   The new list of failed URLs.
    #
    # @return [Array<URI::HTTP>]
    #   The list of failed URLs.
    #
    # @example
    #   agent.failures = ['http://localhost/']
    #
    def failures=(new_failures)
      @failures.clear

      new_failures.each do |url|
        url = URI(url.to_s) unless url.kind_of?(URI)

        @failures << url
      end

      return @failures
    end

    #
    # Determines whether a given URL could not be visited.
    #
    # @param [URI::HTTP, String] url
    #   The URL to check for failures.
    #
    # @return [Boolean]
    #   Specifies whether the given URL was unable to be visited.
    #
    def failed?(url)
      url = URI(url.to_s) unless url.kind_of?(URI)

      return @failures.include?(url)
    end

    alias pending_urls queue

    #
    # Sets the queue of URLs to visit.
    #
    # @param [#each] new_queue
    #   The new list of URLs to visit.
    #
    # @return [Array<URI::HTTP>]
    #   The list of URLs to visit.
    #
    # @example
    #   agent.queue = ['http://www.vimeo.com/', 'http://www.reddit.com/']
    #
    def queue=(new_queue)
      @queue.clear

      new_queue.each do |url|
        url = URI(url.to_s) unless url.kind_of?(URI)

        @queue << url
      end

      return @queue
    end

    #
    # Determines whether a given URL has been enqueued.
    #
    # @param [URI::HTTP] url
    #   The URL to search for in the queue.
    #
    # @return [Boolean]
    #   Specifies whether the given URL has been queued for visiting.
    #
    def queued?(url)
      @queue.include?(url)
    end

    #
    # Enqueues a given URL for visiting, only if it passes all of the
    # agent's rules for visiting a given URL.
    #
    # @param [URI::HTTP, String] url
    #   The URL to enqueue for visiting.
    #
    # @return [Boolean]
    #   Specifies whether the URL was enqueued, or ignored.
    #
    def enqueue(url,level=0)
      url = sanitize_url(url)

      if (!(queued?(url)) && visit?(url))
        link = url.to_s

        begin
          @every_url_blocks.each { |url_block| url_block.call(url) }

          @every_url_like_blocks.each do |pattern,url_blocks|
            match = case pattern
                    when Regexp
                      link =~ pattern
                    else
                      (pattern == link) || (pattern == url)
                    end

            if match
              url_blocks.each { |url_block| url_block.call(url) }
            end
          end
        rescue Actions::Paused => action
          raise(action)
        rescue Actions::SkipLink
          return false
        rescue Actions::Action
        end

        @queue << url
        @levels[url] = level
        return true
      end

      return false
    end

    #
    # Requests and creates a new Page object from a given URL.
    #
    # @param [URI::HTTP] url
    #   The URL to request.
    #
    # @yield [page]
    #   If a block is given, it will be passed the page that represents the
    #   response.
    #
    # @yieldparam [Page] page
    #   The page for the response.
    #
    # @return [Page, nil]
    #   The page for the response, or `nil` if the request failed.
    #
    def get_page(url)
      url = URI(url.to_s)

      prepare_request(url) do |session,path,headers|
        new_page = Page.new(url,session.get(path,headers))

        # save any new cookies
        @cookies.from_page(new_page)

        yield new_page if block_given?
        return new_page
      end
    end

    #
    # Posts supplied form data and creates a new Page object from a given URL.
    #
    # @param [URI::HTTP] url
    #   The URL to request.
    #
    # @param [String] post_data
    #   Form option data.
    #
    # @yield [page]
    #   If a block is given, it will be passed the page that represents the
    #   response.
    #
    # @yieldparam [Page] page
    #   The page for the response.
    #
    # @return [Page, nil]
    #   The page for the response, or `nil` if the request failed.
    #
    # @since 0.2.2
    #
    def post_page(url,post_data='')
      url = URI(url.to_s) unless url.kind_of?(URI)

      prepare_request(url) do |session,path,headers|
        new_page = Page.new(url,session.post(path,post_data,headers))

        # save any new cookies
        @cookies.from_page(new_page)

        yield new_page if block_given?
        return new_page
      end
    end

    #
    # Visits a given URL, and enqueus the links recovered from the URL
    # to be visited later.
    #
    # @param [URI::HTTP, String] url
    #   The URL to visit.
    #
    # @yield [page]
    #   If a block is given, it will be passed the page which was visited.
    #
    # @yieldparam [Page] page
    #   The page which was visited.
    #
    # @return [Page, nil]
    #   The page that was visited. If `nil` is returned, either the request
    #   for the page failed, or the page was skipped.
    #
    def visit_page(url)
      url = sanitize_url(url)

      get_page(url) do |page|
        @history << page.url

        begin
          @every_page_blocks.each { |page_block| page_block.call(page) }

          yield page if block_given?
        rescue Actions::Paused => action
          raise(action)
        rescue Actions::SkipPage
          return nil
        rescue Actions::Action
        end

        page.each_url do |next_url|
          begin
            @every_link_blocks.each do |link_block|
              link_block.call(page.url,next_url)
            end
          rescue Actions::Paused => action
            raise(action)
          rescue Actions::SkipLink
            next
          rescue Actions::Action
          end

          if (@max_depth.nil? || @max_depth > @levels[url])
            enqueue(next_url,@levels[url] + 1)
          end
        end
      end
    end

    #
    # Converts the agent into a Hash.
    #
    # @return [Hash]
    #   The agent represented as a Hash containing the `history` and
    #   the `queue` of the agent.
    #
    def to_hash
      {:history => @history, :queue => @queue}
    end

    protected

    #
    # Normalizes the request path and grabs a session to handle page
    # get and post requests.
    #
    # @param [URI::HTTP] url
    #   The URL to request.
    #
    # @yield [request]
    #   A block whose purpose is to make a page request.
    #
    # @yieldparam [Net::HTTP] session
    #   An HTTP session object.
    #
    # @yieldparam [String] path
    #   Normalized URL string.
    #
    # @yieldparam [Hash] headers
    #   A Hash of request header options.
    #
    # @since 0.2.2
    #
    def prepare_request(url,&block)
      host = url.host
      port = url.port
      path = unless url.path.empty?
               url.path
             else
               '/'
             end

      # append the URL query to the path
      path += "?#{url.query}" if url.query

      # set any additional HTTP headers
      headers = {}

      unless @host_headers.empty?
        @host_headers.each do |name,header|
          if host.match(name)
            headers['Host'] = header
            break
          end
        end
      end

      headers['Host']     ||= @host_header if @host_header
      headers['User-Agent'] = @user_agent if @user_agent
      headers['Referer']    = @referer if @referer

      if (authorization = @authorized.for_url(url))
        headers['Authorization'] = "Basic #{authorization}"
      end

      if (header_cookies = @cookies.for_host(url.host))
        headers['Cookie'] = header_cookies
      end

      begin
        sleep(@delay) if @delay > 0

        yield @sessions[url], path, headers
      rescue SystemCallError,
             Timeout::Error,
             SocketError,
             IOError,
             OpenSSL::SSL::SSLError,
             Net::HTTPBadResponse

        @sessions.kill!(url)

        failed(url)
        return nil
      end
    end

    #
    # Dequeues a URL that will later be visited.
    #
    # @return [URI::HTTP]
    #   The URL that was at the front of the queue.
    #
    def dequeue
      @queue.shift
    end

    #
    # Determines if a given URL should be visited.
    #
    # @param [URI::HTTP] url
    #   The URL in question.
    #
    # @return [Boolean]
    #   Specifies whether the given URL should be visited.
    #
    def visit?(url)
      !visited?(url) &&
       visit_scheme?(url.scheme) &&
       visit_host?(url.host) &&
       visit_port?(url.port) &&
       visit_link?(url.to_s) &&
       visit_url?(url) &&
       visit_ext?(url.path)
    end

    #
    # Adds a given URL to the failures list.
    #
    # @param [URI::HTTP] url
    #   The URL to add to the failures list.
    #
    def failed(url)
      @failures << url
      @every_failed_url_blocks.each { |fail_block| fail_block.call(url) }
      return true
    end

  end
end


text:
//This file has been modified with suggestions from Forum Users.

using System;
using Reporting.Rdl;
using System.IO;
using System.Collections;
using System.Text;

namespace Reporting.Rdl
{
	//TODO: Implement MemoryStreamGen rendering from Report.cs (search for RunRenderXmlTransform) josh 1/9/11
	///<summary>
	///The primary class to "run" a report to XML
	///</summary>
	internal class RenderXml: IPresent
	{
		Report r;					// report
		TextWriter tw;				// where the output is going
		Stack stkReportItem;		// stack of nested report items
		Stack stkContainers;		// stack to hold container elements
		string rowstart=null;

		public RenderXml(Report rep, IStreamGen sg)
		{
			r = rep;
			tw = sg.GetTextWriter();
			stkReportItem = new Stack();
			stkContainers = new Stack();
		}

        //Replaced from forum, User: Aulofee http://www.fyireporting.com/forum/viewtopic.php?t=793
        public void Dispose() { }

		public Report Report()
		{
			return r;
		}

		public bool IsPagingNeeded()
		{
			return false;
		}

		public void Start()
		{
			tw.WriteLine("<?xml version='1.0' encoding='UTF-8'?>");

			PushContainer(r.ReportDefinition.DataElementName);

			return;
		}

		public void End()
		{
			ContainerIO cio = (ContainerIO) stkContainers.Pop();	// this pop should empty the stack
			cio.WriteAttribute(">");
			tw.WriteLine(cio.attribute_sb);
			tw.WriteLine(cio.subelement_sb);
			tw.WriteLine("</" + r.ReportDefinition.DataElementName + ">");

			return;
		}

		// Body: main container for the report
		public void BodyStart(Body b)
		{
		}

		public void BodyEnd(Body b)
		{
		}

		public void PageHeaderStart(PageHeader ph)
		{
		}

		public void PageHeaderEnd(PageHeader ph)
		{
		}

		public void PageFooterStart(PageFooter pf)
		{
		}

		public void PageFooterEnd(PageFooter pf)
		{
		}

		public void Textbox(Textbox tb, string t, Row row)
		{
			if (tb.DataElementOutput != DataElementOutputEnum.Output ||
				tb.DataElementName == null)
				return;

			if (rowstart != null)		// In case no items in row are visible
			{							//   we delay until we get one.
//				WriteElement(rowstart);
				rowstart = null;
			}
			t = Xml.ToXmlAnsi(t);
			if (tb.DataElementStyle == DataElementStyleEnum.AttributeNormal)
			{	// write out as attribute
				WriteAttribute(" {0}='{1}'",
								tb.DataElementName, Xml.EscapeXmlAttribute(t));
			}
			else
			{	// write out as element
				WriteElement("<{0}>{1}</{0}>", tb.DataElementName, t);
			}
		}

		public void DataRegionNoRows(DataRegion t, string noRowMsg)
		{
		}

		// Lists
		public bool ListStart(List l, Row r)
		{
			if (l.DataElementOutput == DataElementOutputEnum.NoOutput)
				return false;
			if (l.DataElementOutput == DataElementOutputEnum.ContentsOnly)
				return true;
			WriteElementLine("<{0}>", l.DataElementName);

			return true;	//want to continue
		}

		public void ListEnd(List l, Row r)
		{
			if (l.DataElementOutput == DataElementOutputEnum.NoOutput ||
				l.DataElementOutput == DataElementOutputEnum.ContentsOnly)
				return;

			WriteElementLine("</{0}>", l.DataElementName);
			return;
		}

		public void ListEntryBegin(List l, Row r)
		{
			string d;
			if (l.Grouping == null)
			{
				if (l.DataElementOutput != DataElementOutputEnum.Output)
					return;
				d = string.Format("<{0}", l.DataInstanceName);
			}
			else
			{
				Grouping g = l.Grouping;
				if (g.DataElementOutput != DataElementOutputEnum.Output)
					return;
				d = string.Format("<{0}", l.DataInstanceName);
			}
			PushContainer(l.DataInstanceName);

			return;
		}

		public void ListEntryEnd(List l, Row r)
		{
			if (l.DataElementOutput != DataElementOutputEnum.Output)
				return;

			PopContainer(l.DataInstanceName);

		}

		// Tables					// Report item table
		public bool TableStart(Table t, Row row)
		{
			if (t.DataElementOutput == DataElementOutputEnum.NoOutput)
				return false;

			PushContainer(t.DataElementName);

			stkReportItem.Push(t);
			string cName = TableGetCollectionName(t);
			if (cName != null)
				WriteAttributeLine("><{0}", cName);

			return true;
		}

		public void TableEnd(Table t, Row row)
		{
			if (t.DataElementOutput == DataElementOutputEnum.NoOutput)
				return;

			string cName = TableGetCollectionName(t);
			PopContainer(cName);

			WriteElementLine("</{0}>", t.DataElementName);
			stkReportItem.Pop();
			return;
		}

		string TableGetCollectionName(Table t)
		{
			string cName;
			if (t.TableGroups == null)
			{
				if (t.Details != null && t.Details.Grouping != null)
					cName = t.Details.Grouping.DataCollectionName;
				else
					cName = t.DetailDataCollectionName;
			}
			else
				cName = null;

			return cName;
		}

		public void TableBodyStart(Table t, Row row)
		{
		}

		public void TableBodyEnd(Table t, Row row)
		{
		}

		public void TableFooterStart(Footer f, Row row)
		{
		}

		public void TableFooterEnd(Footer f, Row row)
		{
		}

		public void TableHeaderStart(Header h, Row row)
		{
		}

		public void TableHeaderEnd(Header h, Row row)
		{
		}

		public void TableRowStart(TableRow tr, Row row)
		{
			string n = TableGetRowElementName(tr);
			if (n == null)
				return;
			PushContainer(n);
		}

		public void TableRowEnd(TableRow tr, Row row)
		{
			string n = TableGetRowElementName(tr);
			if (n == null)
				return;
			this.PopContainer(n);
		}

		string TableGetRowElementName(TableRow tr)
		{
			for (ReportLink rl = tr.Parent; !(rl is Table); rl = rl.Parent)
			{
				if (rl is Header || rl is Footer)
					return null;

				if (rl is TableGroup)
				{
					TableGroup tg = rl as TableGroup;
					Grouping g = tg.Grouping;
					return g.DataElementName;
				}

				if (rl is Details)
				{
					Table t = (Table) stkReportItem.Peek();

                    return t.DetailDataElementOutput == DataElementOutputEnum.NoOutput?
                        null: t.DetailDataElementName;
				}
			}

			return null;
		}

		public void TableCellStart(TableCell t, Row row)
		{
			return;
		}

		public void TableCellEnd(TableCell t, Row row)
		{
			return;
		}

        public bool MatrixStart(Matrix m, MatrixCellEntry[,] matrix, Row r, int headerRows, int maxRows, int maxCols)				// called first
		{
			if (m.DataElementOutput != DataElementOutputEnum.Output)
				return false;
			tw.WriteLine("<" + (m.DataElementName == null? "Matrix": m.DataElementName) + ">");

			return true;
		}

		public void MatrixColumns(Matrix m, MatrixColumns mc)	// called just after MatrixStart
		{
		}

		public void MatrixCellStart(Matrix m, ReportItem ri, int row, int column, Row r, float h, float w, int colSpan)
		{
		}

		public void MatrixCellEnd(Matrix m, ReportItem ri, int row, int column, Row r)
		{
		}

		public void MatrixRowStart(Matrix m, int row, Row r)
		{
		}

		public void MatrixRowEnd(Matrix m, int row, Row r)
		{
		}

		public void MatrixEnd(Matrix m, Row r)				// called last
		{
			tw.WriteLine("</" + (m.DataElementName == null? "Matrix": m.DataElementName) + ">");
		}

		public void Chart(Chart c, Row r, ChartBase cb)
		{
		}

		public void Image(Image i, Row r, string mimeType, Stream io)
		{
		}

		public void Line(Line l, Row r)
		{
		}

		public bool RectangleStart(Rdl.Rectangle rect, Row r)
		{
			bool rc=true;
			switch (rect.DataElementOutput)
			{
				case DataElementOutputEnum.NoOutput:
					rc = false;
					break;
				case DataElementOutputEnum.Output:
					if (rowstart != null)		// In case no items in row are visible
					{							//   we delay until we get one.
						tw.Write(rowstart);
						rowstart = null;
					}
					PushContainer(rect.DataElementName);
					break;
				case DataElementOutputEnum.Auto:
				case DataElementOutputEnum.ContentsOnly:
				default:
					break;
			}

			return rc;
		}

		public void RectangleEnd(Rdl.Rectangle rect, Row r)
		{
			if (rect.DataElementOutput != DataElementOutputEnum.Output)
				return;
			PopContainer(rect.DataElementName);
		}

		public void Subreport(Subreport s, Row r)
		{
			if (s.DataElementOutput != DataElementOutputEnum.Output)
				return;

			PushContainer(s.DataElementName);

			s.ReportDefn.Run(this);

			PopContainer(s.DataElementName);
			return;
		}
		public void GroupingStart(Grouping g)			// called at start of grouping
		{
			if (g.DataElementOutput != DataElementOutputEnum.Output)
				return;

			PushContainer(g.DataCollectionName);

		}
		public void GroupingInstanceStart(Grouping g)	// called at start for each grouping instance
		{
			if (g.DataElementOutput != DataElementOutputEnum.Output)
				return;
			PushContainer(g.DataElementName);

		}
		public void GroupingInstanceEnd(Grouping g)	// called at start for each grouping instance
		{
			if (g.DataElementOutput != DataElementOutputEnum.Output)
				return;
			PopContainer(g.DataElementName);
		}
		public void GroupingEnd(Grouping g)			// called at end of grouping
		{
			if (g.DataElementOutput != DataElementOutputEnum.Output)
				return;
			PopContainer(g.DataCollectionName);
		}

		public void RunPages(Pages pgs)	// we don't have paging turned on for xml
		{
			return;
		}

		void PopContainer(string name)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Pop();
			if (cio.bEmpty)
				return;
			cio.WriteAttribute(">");
			WriteElementLine(cio.attribute_sb.ToString());
			WriteElementLine(cio.subelement_sb.ToString());
			if (name != null)
				WriteElementLine("</{0}>", name);
		}

		void PushContainer(string name)
		{
			ContainerIO cio = new ContainerIO("<" + name);
			stkContainers.Push(cio);
		}

		void WriteElement(string format)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Peek();
			cio.WriteElement(format);
		}

		void WriteElement(string format, params object[] arg)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Peek();
			cio.WriteElement(format, arg);
		}

		void WriteElementLine(string format)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Peek();
			cio.WriteElementLine(format);
		}

		void WriteElementLine(string format, params object[] arg)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Peek();
			cio.WriteElementLine(format, arg);
		}

		void WriteAttribute(string format)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Peek();
			cio.WriteAttribute(format);
		}

		void WriteAttribute(string format, params object[] arg)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Peek();
			cio.WriteAttribute(format, arg);
		}

		void WriteAttributeLine(string format)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Peek();
			cio.WriteAttributeLine(format);
		}

		void WriteAttributeLine(string format, params object[] arg)
		{
			ContainerIO cio = (ContainerIO) this.stkContainers.Peek();
			cio.WriteAttributeLine(format, arg);
		}

		class ContainerIO
		{
			internal StringBuilder attribute_sb;
			internal StringWriter attribute_sw;

			internal StringBuilder subelement_sb;
			internal StringWriter subelement_sw;

			internal bool bEmpty=true;

			internal ContainerIO(string begin)
			{
				subelement_sb = new StringBuilder();
				subelement_sw = new StringWriter(subelement_sb);

				attribute_sb = new StringBuilder(begin);
				attribute_sw = new StringWriter(attribute_sb);
			}

			internal void WriteElement(string format)
			{
				bEmpty = false;
				subelement_sw.Write(format);
			}

			internal void WriteElement(string format, params object[] arg)
			{
				bEmpty = false;
				subelement_sw.Write(format, arg);
			}

			internal void WriteElementLine(string format)
			{
				bEmpty = false;
				subelement_sw.WriteLine(format);
			}

			internal void WriteElementLine(string format, params object[] arg)
			{
				bEmpty = false;
				subelement_sw.WriteLine(format, arg);
			}

			internal void WriteAttribute(string format)
			{
				bEmpty = false;
				attribute_sw.Write(format);
			}

			internal void WriteAttribute(string format, params object[] arg)
			{
				bEmpty = false;
				attribute_sw.Write(format, arg);
			}

			internal void WriteAttributeLine(string format)
			{
				bEmpty = false;
				attribute_sw.WriteLine(format);
			}

			internal void WriteAttributeLine(string format, params object[] arg)
			{
				bEmpty = false;
				attribute_sw.WriteLine(format, arg);
			}

		}
	}

}