text:
// This file contains declarations of interfaces that wrap SDP-related
// constructs; session descriptions and ICE candidates. The inner "cricket::"
// objects shouldn't be accessed directly; the intention is that an application
// using the PeerConnection API only creates these objects from strings, and
// them passes them into the PeerConnection.
//
// Though in the future, we're planning to provide an SDP parsing API, with a
// structure more friendly than cricket::SessionDescription.

#ifndef API_JSEP_H_
#define API_JSEP_H_

#include <stddef.h>

#include <memory>
#include <string>
#include <vector>

#include "absl/types/optional.h"
#include "api/rtc_error.h"
#include "rtc_base/deprecation.h"
#include "rtc_base/ref_count.h"
#include "rtc_base/system/rtc_export.h"

namespace cricket {
class Candidate;
class SessionDescription;
}  // namespace cricket

namespace webrtc {

struct SdpParseError {
 public:
  // The sdp line that causes the error.
  std::string line;
  // Explains the error.
  std::string description;
};

// Class representation of an ICE candidate.
//
// An instance of this interface is supposed to be owned by one class at
// a time and is therefore not expected to be thread safe.
//
// An instance can be created by CreateIceCandidate.
class RTC_EXPORT IceCandidateInterface {
 public:
  virtual ~IceCandidateInterface() {}
  // If present, this is the value of the "a=mid" attribute of the candidate's
  // m= section in SDP, which identifies the m= section.
  virtual std::string sdp_mid() const = 0;
  // This indicates the index (starting at zero) of m= section this candidate
  // is associated with. Needed when an endpoint doesn't support MIDs.
  virtual int sdp_mline_index() const = 0;
  // Only for use internally.
  virtual const cricket::Candidate& candidate() const = 0;
  // The URL of the ICE server which this candidate was gathered from.
  // TODO(zhihuang): Remove the default implementation once the subclasses
  // implement this method.
  virtual std::string server_url() const;
  // Creates a SDP-ized form of this candidate.
  virtual bool ToString(std::string* out) const = 0;
};

// Creates a IceCandidateInterface based on SDP string.
// Returns null if the sdp string can't be parsed.
// |error| may be null.
RTC_EXPORT IceCandidateInterface* CreateIceCandidate(const std::string& sdp_mid,
                                                     int sdp_mline_index,
                                                     const std::string& sdp,
                                                     SdpParseError* error);

// Creates an IceCandidateInterface based on a parsed candidate structure.
RTC_EXPORT std::unique_ptr<IceCandidateInterface> CreateIceCandidate(
    const std::string& sdp_mid,
    int sdp_mline_index,
    const cricket::Candidate& candidate);

// This class represents a collection of candidates for a specific m= section.
// Used in SessionDescriptionInterface.
class IceCandidateCollection {
 public:
  virtual ~IceCandidateCollection() {}
  virtual size_t count() const = 0;
  // Returns true if an equivalent |candidate| exist in the collection.
  virtual bool HasCandidate(const IceCandidateInterface* candidate) const = 0;
  virtual const IceCandidateInterface* at(size_t index) const = 0;
};

// Enum that describes the type of the SessionDescriptionInterface.
// Corresponds to RTCSdpType in the WebRTC specification.
// https://w3c.github.io/webrtc-pc/#dom-rtcsdptype
enum class SdpType {
  kOffer,     // Description must be treated as an SDP offer.
  kPrAnswer,  // Description must be treated as an SDP answer, but not a final
              // answer.
  kAnswer,    // Description must be treated as an SDP final answer, and the
              // offer-answer exchange must be considered complete after
              // receiving this.
  kRollback   // Resets any pending offers and sets signaling state back to
              // stable.
};

// Returns the string form of the given SDP type. String forms are defined in
// SessionDescriptionInterface.
RTC_EXPORT const char* SdpTypeToString(SdpType type);

// Returns the SdpType from its string form. The string form can be one of the
// constants defined in SessionDescriptionInterface. Passing in any other string
// results in nullopt.
absl::optional<SdpType> SdpTypeFromString(const std::string& type_str);

// Class representation of an SDP session description.
//
// An instance of this interface is supposed to be owned by one class at a time
// and is therefore not expected to be thread safe.
//
// An instance can be created by CreateSessionDescription.
class RTC_EXPORT SessionDescriptionInterface {
 public:
  // String representations of the supported SDP types.
  static const char kOffer[];
  static const char kPrAnswer[];
  static const char kAnswer[];
  static const char kRollback[];

  virtual ~SessionDescriptionInterface() {}

  // Only for use internally.
  virtual cricket::SessionDescription* description() = 0;
  virtual const cricket::SessionDescription* description() const = 0;

  // Get the session id and session version, which are defined based on
  // RFC 4566 for the SDP o= line.
  virtual std::string session_id() const = 0;
  virtual std::string session_version() const = 0;

  // Returns the type of this session description as an SdpType. Descriptions of
  // the various types are found in the SdpType documentation.
  // TODO(steveanton): Remove default implementation once Chromium has been
  // updated.
  virtual SdpType GetType() const;

  // kOffer/kPrAnswer/kAnswer
  // TODO(steveanton): Remove this in favor of |GetType| that returns SdpType.
  virtual std::string type() const = 0;

  // Adds the specified candidate to the description.
  //
  // Ownership is not transferred.
  //
  // Returns false if the session description does not have a media section
  // that corresponds to |candidate.sdp_mid()| or
  // |candidate.sdp_mline_index()|.
  virtual bool AddCandidate(const IceCandidateInterface* candidate) = 0;

  // Removes the candidates from the description, if found.
  //
  // Returns the number of candidates removed.
  virtual size_t RemoveCandidates(
      const std::vector<cricket::Candidate>& candidates);

  // Returns the number of m= sections in the session description.
  virtual size_t number_of_mediasections() const = 0;

  // Returns a collection of all candidates that belong to a certain m=
  // section.
  virtual const IceCandidateCollection* candidates(
      size_t mediasection_index) const = 0;

  // Serializes the description to SDP.
  virtual bool ToString(std::string* out) const = 0;
};

// Creates a SessionDescriptionInterface based on the SDP string and the type.
// Returns null if the sdp string can't be parsed or the type is unsupported.
// |error| may be null.
// TODO(steveanton): This function is deprecated. Please use the functions below
// which take an SdpType enum instead. Remove this once it is no longer used.
RTC_EXPORT SessionDescriptionInterface* CreateSessionDescription(
    const std::string& type,
    const std::string& sdp,
    SdpParseError* error);

// Creates a SessionDescriptionInterface based on the SDP string and the type.
// Returns null if the SDP string cannot be parsed.
// If using the signature with |error_out|, details of the parsing error may be
// written to |error_out| if it is not null.
RTC_EXPORT std::unique_ptr<SessionDescriptionInterface>
CreateSessionDescription(SdpType type, const std::string& sdp);
RTC_EXPORT std::unique_ptr<SessionDescriptionInterface>
CreateSessionDescription(SdpType type,
                         const std::string& sdp,
                         SdpParseError* error_out);

// Creates a SessionDescriptionInterface based on a parsed SDP structure and the
// given type, ID and version.
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
    SdpType type,
    const std::string& session_id,
    const std::string& session_version,
    std::unique_ptr<cricket::SessionDescription> description);

// CreateOffer and CreateAnswer callback interface.
class RTC_EXPORT CreateSessionDescriptionObserver
    : public rtc::RefCountInterface {
 public:
  // This callback transfers the ownership of the |desc|.
  // TODO(deadbeef): Make this take an std::unique_ptr<> to avoid confusion
  // around ownership.
  virtual void OnSuccess(SessionDescriptionInterface* desc) = 0;
  // The OnFailure callback takes an RTCError, which consists of an
  // error code and a string.
  // RTCError is non-copyable, so it must be passed using std::move.
  // Earlier versions of the API used a string argument. This version
  // is removed; its functionality was the same as passing
  // error.message.
  virtual void OnFailure(RTCError error) = 0;

 protected:
  ~CreateSessionDescriptionObserver() override = default;
};

// SetLocalDescription and SetRemoteDescription callback interface.
class RTC_EXPORT SetSessionDescriptionObserver : public rtc::RefCountInterface {
 public:
  virtual void OnSuccess() = 0;
  // See description in CreateSessionDescriptionObserver for OnFailure.
  virtual void OnFailure(RTCError error) = 0;

 protected:
  ~SetSessionDescriptionObserver() override = default;
};

}  // namespace webrtc

#endif  // API_JSEP_H_


text:
package ast

import (
	"encoding/json"
	"fmt"
	"testing"
)

// Test of example code in docs/content/annotations.md
func ExampleAnnotationSet_Flatten() {
	modules := [][]string{
		{
			"foo.rego", `# METADATA
# scope: subpackages
# organizations:
# - Acme Corp.
package foo`},
		{
			"mod", `# METADATA
# description: A couple of useful rules
package foo.bar

# METADATA
# title: My Rule P
p := 7`},
	}

	parsed := make([]*Module, 0, len(modules))
	for _, entry := range modules {
		pm, err := ParseModuleWithOpts(entry[0], entry[1], ParserOptions{ProcessAnnotation: true})
		if err != nil {
			panic(err)
		}
		parsed = append(parsed, pm)
	}

	as, err := BuildAnnotationSet(parsed)
	if err != nil {
		panic(err)
	}

	flattened := as.Flatten()
	for _, entry := range flattened {
		fmt.Printf("%v at %v has annotations %v\n",
			entry.Path,
			entry.Location,
			entry.Annotations)
	}

	// Output:
	// data.foo at foo.rego:5 has annotations {"scope":"subpackages","organizations":["Acme Corp."]}
	// data.foo.bar at mod:3 has annotations {"scope":"package","description":"A couple of useful rules"}
	// data.foo.bar.p at mod:7 has annotations {"scope":"rule","title":"My Rule P"}
}

// Test of example code in docs/content/annotations.md
func ExampleAnnotationSet_Chain() {
	modules := [][]string{
		{
			"foo.rego", `# METADATA
# scope: subpackages
# organizations:
# - Acme Corp.
package foo`},
		{
			"mod", `# METADATA
# description: A couple of useful rules
package foo.bar

# METADATA
# title: My Rule P
p := 7`},
	}

	parsed := make([]*Module, 0, len(modules))
	for _, entry := range modules {
		pm, err := ParseModuleWithOpts(entry[0], entry[1], ParserOptions{ProcessAnnotation: true})
		if err != nil {
			panic(err)
		}
		parsed = append(parsed, pm)
	}

	as, err := BuildAnnotationSet(parsed)
	if err != nil {
		panic(err)
	}

	rule := parsed[1].Rules[0]

	flattened := as.Chain(rule)
	for _, entry := range flattened {
		fmt.Printf("%v at %v has annotations %v\n",
			entry.Path,
			entry.Location,
			entry.Annotations)
	}

	// Output:
	// data.foo.bar.p at mod:7 has annotations {"scope":"rule","title":"My Rule P"}
	// data.foo.bar at mod:3 has annotations {"scope":"package","description":"A couple of useful rules"}
	// data.foo at foo.rego:5 has annotations {"scope":"subpackages","organizations":["Acme Corp."]}
}

func TestAnnotationSet_Flatten(t *testing.T) {
	tests := []struct {
		note     string
		modules  map[string]string
		expected []AnnotationsRef
	}{
		{
			note:     "no modules",
			modules:  map[string]string{},
			expected: []AnnotationsRef{},
		},
		{
			note: "simple module (all annotation types)",
			modules: map[string]string{
				"module": `# METADATA
# title: pkg
# description: pkg
# organizations:
# - pkg
# related_resources:
# - https://pkg
# authors:
# - pkg
# schemas:
# - input: {"type": "boolean"}
# custom:
#  pkg: pkg
package test

# METADATA
# scope: document
# title: doc
# description: doc
# organizations:
# - doc
# related_resources:
# - https://doc
# authors:
# - doc
# schemas:
# - input: {"type": "integer"}
# custom:
#  doc: doc

# METADATA
# title: rule
# description: rule
# organizations:
# - rule
# related_resources:
# - https://rule
# authors:
# - rule
# schemas:
# - input: {"type": "string"}
# custom:
#  rule: rule
p = 1`,
			},
			expected: []AnnotationsRef{
				{
					Path:     MustParseRef("data.test"),
					Location: &Location{File: "module", Row: 14},
					Annotations: &Annotations{
						Scope:         "package",
						Title:         "pkg",
						Description:   "pkg",
						Organizations: []string{"pkg"},
						RelatedResources: []*RelatedResourceAnnotation{
							{
								Ref: mustParseURL("https://pkg"),
							},
						},
						Authors: []*AuthorAnnotation{
							{
								Name: "pkg",
							},
						},
						Schemas: []*SchemaAnnotation{
							schemaAnnotationFromMap("input", map[string]interface{}{
								"type": "boolean",
							}),
						},
						Custom: map[string]interface{}{
							"pkg": "pkg",
						},
					},
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 44},
					Annotations: &Annotations{
						Scope:         "document",
						Title:         "doc",
						Description:   "doc",
						Organizations: []string{"doc"},
						RelatedResources: []*RelatedResourceAnnotation{
							{
								Ref: mustParseURL("https://doc"),
							},
						},
						Authors: []*AuthorAnnotation{
							{
								Name: "doc",
							},
						},
						Schemas: []*SchemaAnnotation{
							schemaAnnotationFromMap("input", map[string]interface{}{
								"type": "integer",
							}),
						},
						Custom: map[string]interface{}{
							"doc": "doc",
						},
					},
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 44},
					Annotations: &Annotations{
						Scope:         "rule",
						Title:         "rule",
						Description:   "rule",
						Organizations: []string{"rule"},
						RelatedResources: []*RelatedResourceAnnotation{
							{
								Ref: mustParseURL("https://rule"),
							},
						},
						Authors: []*AuthorAnnotation{
							{
								Name: "rule",
							},
						},
						Schemas: []*SchemaAnnotation{
							schemaAnnotationFromMap("input", map[string]interface{}{
								"type": "string",
							}),
						},
						Custom: map[string]interface{}{
							"rule": "rule",
						},
					},
				},
			},
		},
		{
			note: "multiple subpackages",
			modules: map[string]string{
				"root": `# METADATA
# scope: subpackages
# title: ROOT
package root`,
				"root.foo": `# METADATA
# title: FOO
# scope: subpackages
package root.foo`,
				"root.foo.baz": `# METADATA
# title: BAZ
package root.foo.baz`,
				"root.bar": `# METADATA
# title: BAR
# scope: subpackages
package root.bar`,
				"root.bar.baz": `# METADATA
# title: BAZ
package root.bar.baz`,
				"root2": `# METADATA
# scope: subpackages
# title: ROOT2
package root2`,
			},
			expected: []AnnotationsRef{
				{
					Path:     MustParseRef("data.root"),
					Location: &Location{File: "root", Row: 4},
					Annotations: &Annotations{
						Scope: "subpackages",
						Title: "ROOT",
					},
				},
				{
					Path:     MustParseRef("data.root.bar"),
					Location: &Location{File: "root.bar", Row: 4},
					Annotations: &Annotations{
						Scope: "subpackages",
						Title: "BAR",
					},
				},
				{
					Path:     MustParseRef("data.root.bar.baz"),
					Location: &Location{File: "root.bar.baz", Row: 3},
					Annotations: &Annotations{
						Scope: "package",
						Title: "BAZ",
					},
				},
				{
					Path:     MustParseRef("data.root.foo"),
					Location: &Location{File: "root.foo", Row: 4},
					Annotations: &Annotations{
						Scope: "subpackages",
						Title: "FOO",
					},
				},
				{
					Path:     MustParseRef("data.root.foo.baz"),
					Location: &Location{File: "root.foo.baz", Row: 3},
					Annotations: &Annotations{
						Scope: "package",
						Title: "BAZ",
					},
				},
				{
					Path:     MustParseRef("data.root2"),
					Location: &Location{File: "root2", Row: 4},
					Annotations: &Annotations{
						Scope: "subpackages",
						Title: "ROOT2",
					},
				},
			},
		},
		{
			note: "overlapping package paths",
			modules: map[string]string{
				"mod1": `# METADATA
# title: TEST1
package test`,
				"mod2": `# METADATA
# title: TEST2
package test`,
			},
			expected: []AnnotationsRef{
				{
					Path:     MustParseRef("data.test"),
					Location: &Location{File: "mod1", Row: 3},
					Annotations: &Annotations{
						Scope: "package",
						Title: "TEST1",
					},
				},
				{
					Path:     MustParseRef("data.test"),
					Location: &Location{File: "mod2", Row: 3},
					Annotations: &Annotations{
						Scope: "package",
						Title: "TEST2",
					},
				},
			},
		},
		{
			note: "overlapping rule paths (same module)",
			modules: map[string]string{
				"mod": `package test

# METADATA
# title: P1
p[v] {v = 1}

# METADATA
# title: P2
p[v] {v = 2}`,
			},
			expected: []AnnotationsRef{
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "mod", Row: 5},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "P1",
					},
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "mod", Row: 9},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "P2",
					},
				},
			},
		},
		{
			note: "overlapping rule paths (different modules)",
			modules: map[string]string{
				"mod1": `package test
# METADATA
# title: P1
p[v] {v = 1}`,
				"mod2": `package test
# METADATA
# title: P2
p[v] {v = 2}`,
			},
			expected: []AnnotationsRef{
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "mod1", Row: 4},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "P1",
					},
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "mod2", Row: 4},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "P2",
					},
				},
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.note, func(t *testing.T) {
			compiler := MustCompileModulesWithOpts(tc.modules,
				CompileOpts{ParserOptions: ParserOptions{ProcessAnnotation: true}})

			as := compiler.GetAnnotationSet()
			if as == nil {
				t.Fatalf("Expected compiled AnnotationSet, got nil")
			}

			flattened := as.Flatten()

			if len(flattened) != len(tc.expected) {
				t.Fatalf("flattened AnnotationSet\n%v\ndoesn't match expected\n%v",
					toJSON(flattened), toJSON(tc.expected))
			}

			for i, expected := range tc.expected {
				a := flattened[i]
				if !expected.Path.Equal(a.Path) {
					t.Fatalf("path of AnnotationRef at %d '%v' doesn't match expected '%v'",
						i, a.Path, expected.Path)
				}
				if expected.Location.File != a.Location.File || expected.Location.Row != a.Location.Row {
					t.Fatalf("location of AnnotationRef at %d '%v' doesn't match expected '%v'",
						i, a.Location, expected.Location)
				}
				if expected.Annotations.Compare(a.Annotations) != 0 {
					t.Fatalf("annotations of AnnotationRef at %d\n%v\ndoesn't match expected\n%v",
						i, a.Annotations, expected.Annotations)
				}
			}
		})
	}
}

func TestAnnotationSet_Chain(t *testing.T) {
	tests := []struct {
		note                string
		modules             map[string]string
		moduleToAnalyze     string
		ruleOnLineToAnalyze int
		expected            []AnnotationsRef
	}{
		{
			note: "simple module (all annotation types)",
			modules: map[string]string{
				"module": `# METADATA
# title: pkg
# description: pkg
# organizations:
# - pkg
# related_resources:
# - https://pkg
# authors:
# - pkg
# schemas:
# - input.foo: {"type": "boolean"}
# custom:
#  pkg: pkg
package test

# METADATA
# scope: document
# title: doc
# description: doc
# organizations:
# - doc
# related_resources:
# - https://doc
# authors:
# - doc
# schemas:
# - input.bar: {"type": "integer"}
# custom:
#  doc: doc

# METADATA
# title: rule
# description: rule
# organizations:
# - rule
# related_resources:
# - https://rule
# authors:
# - rule
# schemas:
# - input.baz: {"type": "string"}
# custom:
#  rule: rule
p = 1`,
			},
			moduleToAnalyze:     "module",
			ruleOnLineToAnalyze: 44,
			expected: []AnnotationsRef{
				{ // Rule annotation is always first
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 44},
					Annotations: &Annotations{
						Scope:         "rule",
						Title:         "rule",
						Description:   "rule",
						Organizations: []string{"rule"},
						RelatedResources: []*RelatedResourceAnnotation{
							{
								Ref: mustParseURL("https://rule"),
							},
						},
						Authors: []*AuthorAnnotation{
							{
								Name: "rule",
							},
						},
						Schemas: []*SchemaAnnotation{
							schemaAnnotationFromMap("input.baz", map[string]interface{}{
								"type": "string",
							}),
						},
						Custom: map[string]interface{}{
							"rule": "rule",
						},
					},
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 44},
					Annotations: &Annotations{
						Scope:         "document",
						Title:         "doc",
						Description:   "doc",
						Organizations: []string{"doc"},
						RelatedResources: []*RelatedResourceAnnotation{
							{
								Ref: mustParseURL("https://doc"),
							},
						},
						Authors: []*AuthorAnnotation{
							{
								Name: "doc",
							},
						},
						Schemas: []*SchemaAnnotation{
							schemaAnnotationFromMap("input.bar", map[string]interface{}{
								"type": "integer",
							}),
						},
						Custom: map[string]interface{}{
							"doc": "doc",
						},
					},
				},
				{
					Path:     MustParseRef("data.test"),
					Location: &Location{File: "module", Row: 14},
					Annotations: &Annotations{
						Scope:         "package",
						Title:         "pkg",
						Description:   "pkg",
						Organizations: []string{"pkg"},
						RelatedResources: []*RelatedResourceAnnotation{
							{
								Ref: mustParseURL("https://pkg"),
							},
						},
						Authors: []*AuthorAnnotation{
							{
								Name: "pkg",
							},
						},
						Schemas: []*SchemaAnnotation{
							schemaAnnotationFromMap("input.foo", map[string]interface{}{
								"type": "boolean",
							}),
						},
						Custom: map[string]interface{}{
							"pkg": "pkg",
						},
					},
				},
			},
		},
		{
			note: "no annotations on rule",
			modules: map[string]string{
				"module": `# METADATA
# title: pkg
# description: pkg
package test

# METADATA
# scope: document
# title: doc
# description: doc

p = 1`,
			},
			moduleToAnalyze:     "module",
			ruleOnLineToAnalyze: 11,
			expected: []AnnotationsRef{
				{ // Rule entry is always first, even if no annotations are present
					Path:        MustParseRef("data.test.p"),
					Location:    &Location{File: "module", Row: 11},
					Annotations: nil,
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 11},
					Annotations: &Annotations{
						Scope:       "document",
						Title:       "doc",
						Description: "doc",
					},
				},

				{
					Path:     MustParseRef("data.test"),
					Location: &Location{File: "module", Row: 4},
					Annotations: &Annotations{
						Scope:       "package",
						Title:       "pkg",
						Description: "pkg",
					},
				},
			},
		},
		{
			note: "multiple subpackages",
			modules: map[string]string{
				"root": `# METADATA
# scope: subpackages
# title: ROOT
package root`,
				"root.foo": `# METADATA
# title: FOO
# scope: subpackages
package root.foo`,
				"root.foo.bar": `# METADATA
# scope: subpackages
# description: subpackages scope applied to rule in other module
# title: BAR-sub

# METADATA
# title: BAR-other
# description: This metadata is on the path of the queried rule, but shouldn't show up in the result as it's in a different module.
package root.foo.bar

# METADATA
# scope: document
# description: document scope applied to rule in other module
# title: P-doc
p = 1`,
				"rule": `# METADATA
# title: BAR
package root.foo.bar

# METADATA
# title: P
p = 1`,
			},
			moduleToAnalyze:     "rule",
			ruleOnLineToAnalyze: 7,
			expected: []AnnotationsRef{
				{
					Path:     MustParseRef("data.root.foo.bar.p"),
					Location: &Location{File: "rule", Row: 7},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "P",
					},
				},
				{
					Path:     MustParseRef("data.root.foo.bar.p"),
					Location: &Location{File: "root.foo.bar", Row: 15},
					Annotations: &Annotations{
						Scope:       "document",
						Title:       "P-doc",
						Description: "document scope applied to rule in other module",
					},
				},
				{
					Path:     MustParseRef("data.root.foo.bar"),
					Location: &Location{File: "rule", Row: 3},
					Annotations: &Annotations{
						Scope: "package",
						Title: "BAR",
					},
				},
				{
					Path:     MustParseRef("data.root.foo.bar"),
					Location: &Location{File: "root.foo.bar", Row: 9},
					Annotations: &Annotations{
						Scope:       "subpackages",
						Title:       "BAR-sub",
						Description: "subpackages scope applied to rule in other module",
					},
				},
				{
					Path:     MustParseRef("data.root.foo"),
					Location: &Location{File: "root.foo", Row: 4},
					Annotations: &Annotations{
						Scope: "subpackages",
						Title: "FOO",
					},
				},
				{
					Path:     MustParseRef("data.root"),
					Location: &Location{File: "root", Row: 4},
					Annotations: &Annotations{
						Scope: "subpackages",
						Title: "ROOT",
					},
				},
			},
		},
		{
			note: "multiple metadata blocks for single rule (order)",
			modules: map[string]string{
				"module": `package test

# METADATA
# title: One

# METADATA
# title: Two

# METADATA
# title: Three

# METADATA
# title: Four
p = true`,
			},
			moduleToAnalyze:     "module",
			ruleOnLineToAnalyze: 14,
			expected: []AnnotationsRef{ // Rule annotations order is expected to start closest to the rule, moving out
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 14},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "Four",
					},
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 14},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "Three",
					},
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 14},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "Two",
					},
				},
				{
					Path:     MustParseRef("data.test.p"),
					Location: &Location{File: "module", Row: 14},
					Annotations: &Annotations{
						Scope: "rule",
						Title: "One",
					},
				},
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.note, func(t *testing.T) {
			compiler := MustCompileModulesWithOpts(tc.modules,
				CompileOpts{ParserOptions: ParserOptions{ProcessAnnotation: true}})

			as := compiler.GetAnnotationSet()
			if as == nil {
				t.Fatalf("Expected compiled AnnotationSet, got nil")
			}

			m := compiler.Modules[tc.moduleToAnalyze]
			if m == nil {
				t.Fatalf("no such module: %s", tc.moduleToAnalyze)
			}

			var rule *Rule
			for _, r := range m.Rules {
				if r.Location.Row == tc.ruleOnLineToAnalyze {
					rule = r
					break
				}
			}
			if rule == nil {
				t.Fatalf("no rule found on line %d in module '%s'",
					tc.ruleOnLineToAnalyze, tc.moduleToAnalyze)
			}

			chain := as.Chain(rule)

			if len(chain) != len(tc.expected) {
				t.Fatalf("chained AnnotationSet\n%v\n\ndoesn't match expected\n\n%v",
					toJSON(chain), toJSON(tc.expected))
			}

			for i, expected := range tc.expected {
				a := chain[i]
				if !expected.Path.Equal(a.Path) {
					t.Fatalf("path of AnnotationRef at %d '%v' doesn't match expected '%v'",
						i, a.Path, expected.Path)
				}
				if expected.Location.File != a.Location.File || expected.Location.Row != a.Location.Row {
					t.Fatalf("location of AnnotationRef at %d '%v' doesn't match expected '%v'",
						i, a.Location, expected.Location)
				}
				if expected.Annotations.Compare(a.Annotations) != 0 {
					t.Fatalf("annotations of AnnotationRef at %d\n%v\n\ndoesn't match expected\n\n%v",
						i, a.Annotations, expected.Annotations)
				}
			}
		})
	}
}

func TestAnnotations_toObject(t *testing.T) {
	annotations := Annotations{
		Scope:       annotationScopeRule,
		Title:       "A title",
		Description: "A description",
		Organizations: []string{
			"Acme Corp.",
			"Tyrell Corp.",
		},
		RelatedResources: []*RelatedResourceAnnotation{
			{
				Ref:         mustParseURL("https://example.com"),
				Description: "An example",
			},
			{
				Ref: mustParseURL("https://another.example.com"),
			},
		},
		Authors: []*AuthorAnnotation{
			{
				Name:  "John Doe",
				Email: "john@example.com",
			},
			{
				Name: "Jane Doe",
			},
			{
				Email: "jeff@example.com",
			},
		},
		Schemas: []*SchemaAnnotation{
			{
				Path:   MustParseRef("input.foo"),
				Schema: MustParseRef("schema.a"),
			},
			schemaAnnotationFromMap("input.bar", map[string]interface{}{
				"type": "boolean",
			}),
		},
		Custom: map[string]interface{}{
			"number": 42,
			"float":  2.2,
			"string": "foo bar baz",
			"bool":   true,
			"list": []interface{}{
				"a", "b",
			},
			"list_of_lists": []interface{}{
				[]interface{}{
					"a", "b",
				},
				[]interface{}{
					"b", "c",
				},
			},
			"list_of_maps": []interface{}{
				map[string]interface{}{
					"one": 1,
					"two": 2,
				},
				map[string]interface{}{
					"two":   2,
					"three": 3,
				},
			},
			"map": map[string]interface{}{
				"nested_number": 1,
				"nested_map": map[string]interface{}{
					"do": "re",
					"mi": "fa",
				},
				"nested_list": []interface{}{
					1, 2, 3,
				},
			},
		},
	}

	expected := NewObject(
		Item(StringTerm("scope"), StringTerm(annotationScopeRule)),
		Item(StringTerm("title"), StringTerm("A title")),
		Item(StringTerm("description"), StringTerm("A description")),
		Item(StringTerm("organizations"), ArrayTerm(
			StringTerm("Acme Corp."),
			StringTerm("Tyrell Corp."),
		)),
		Item(StringTerm("related_resources"), ArrayTerm(
			ObjectTerm(
				Item(StringTerm("ref"), StringTerm("https://example.com")),
				Item(StringTerm("description"), StringTerm("An example")),
			),
			ObjectTerm(
				Item(StringTerm("ref"), StringTerm("https://another.example.com")),
			),
		)),
		Item(StringTerm("authors"), ArrayTerm(
			ObjectTerm(
				Item(StringTerm("name"), StringTerm("John Doe")),
				Item(StringTerm("email"), StringTerm("john@example.com")),
			),
			ObjectTerm(
				Item(StringTerm("name"), StringTerm("Jane Doe")),
			),
			ObjectTerm(
				Item(StringTerm("email"), StringTerm("jeff@example.com")),
			),
		)),
		Item(StringTerm("schemas"), ArrayTerm(
			ObjectTerm(
				Item(StringTerm("path"), ArrayTerm(StringTerm("input"), StringTerm("foo"))),
				Item(StringTerm("schema"), ArrayTerm(StringTerm("schema"), StringTerm("a"))),
			),
			ObjectTerm(
				Item(StringTerm("path"), ArrayTerm(StringTerm("input"), StringTerm("bar"))),
				Item(StringTerm("definition"), ObjectTerm(
					Item(StringTerm("type"), StringTerm("boolean")),
				)),
			),
		)),
		Item(StringTerm("custom"), ObjectTerm(
			Item(StringTerm("number"), NumberTerm("42")),
			Item(StringTerm("float"), NumberTerm("2.2")),
			Item(StringTerm("string"), StringTerm("foo bar baz")),
			Item(StringTerm("bool"), BooleanTerm(true)),
			Item(StringTerm("list"), ArrayTerm(
				StringTerm("a"),
				StringTerm("b"),
			)),
			Item(StringTerm("list_of_lists"), ArrayTerm(
				ArrayTerm(
					StringTerm("a"),
					StringTerm("b"),
				),
				ArrayTerm(
					StringTerm("b"),
					StringTerm("c"),
				),
			)),
			Item(StringTerm("list_of_maps"), ArrayTerm(
				ObjectTerm(
					Item(StringTerm("one"), NumberTerm("1")),
					Item(StringTerm("two"), NumberTerm("2")),
				),
				ObjectTerm(
					Item(StringTerm("two"), NumberTerm("2")),
					Item(StringTerm("three"), NumberTerm("3")),
				),
			)),
			Item(StringTerm("map"), ObjectTerm(
				Item(StringTerm("nested_number"), NumberTerm("1")),
				Item(StringTerm("nested_map"), ObjectTerm(
					Item(StringTerm("do"), StringTerm("re")),
					Item(StringTerm("mi"), StringTerm("fa")),
				)),
				Item(StringTerm("nested_list"), ArrayTerm(
					NumberTerm("1"),
					NumberTerm("2"),
					NumberTerm("3"),
				)),
			)),
		)),
	)

	obj, err := annotations.toObject()
	if err != nil {
		t.Fatalf("unexpected error: %s", err.Error())
	}

	if Compare(*obj, expected) != 0 {
		t.Fatalf("object generated from annotations\n\n%v\n\ndoesn't match expected\n\n%v",
			*obj, expected)
	}
}

func toJSON(v interface{}) string {
	b, _ := json.Marshal(v)
	return string(b)
}

func schemaAnnotationFromMap(path string, def map[string]interface{}) *SchemaAnnotation {
	var p interface{} = def
	return &SchemaAnnotation{Path: MustParseRef(path), Definition: &p}
}

redpajama_set_name: RedPajamaGithub


text:
package com.examw.collector.service.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;

import org.apache.log4j.Logger;
import org.jsoup.Jsoup;
import org.springframework.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.examw.collector.domain.AdVideo;
import com.examw.collector.domain.Catalog;
import com.examw.collector.domain.Pack;
import com.examw.collector.domain.Relate;
import com.examw.collector.domain.SubClass;
import com.examw.collector.domain.Subject;
import com.examw.collector.domain.local.TeacherEntity;
import com.examw.collector.service.IDataServer;
import com.examw.collector.service.IRemoteDataProxy;
import com.examw.utils.HttpUtil;
import com.examw.utils.XmlUtil;

/**
 * 数据服务实现。
 *
 * @author yangyong.
 * @since 2014-06-26.
 */
public class DataServerImpl implements IDataServer {
	private static Logger logger = Logger.getLogger(DataServerImpl.class);
	private IRemoteDataProxy remoteDataProxy;

	private String savePath;
	public static String ID_PREFIX = "e";
	/**
	 * 设置 图片文件保存位置
	 * @param savePath
	 *
	 */
	public void setSavePath(String savePath) {
		this.savePath = savePath;
	}

	/**
	 * 设置远程数据服务接口。
	 *
	 * @param remoteDataProxy
	 */
	public void setRemoteDataProxy(IRemoteDataProxy remoteDataProxy) {
		this.remoteDataProxy = remoteDataProxy;
	}

	/*
	 * 加载课程类别集合。
	 *
	 * @see com.examw.collector.service.IDataServer#loadCatalogs()
	 */
	@Override
	public List<Catalog> loadCatalogs() {
		try {
			logger.info("加载课程类别集合...");
			String xml = this.remoteDataProxy.loadLesson(1, null, null, null);
			if (StringUtils.isEmpty(xml))
				return null;
			Document document = XmlUtil.loadDocument(xml);
			Element root = document.getDocumentElement();
			NodeList list = XmlUtil.selectNodes(root, ".//up_type");
			if (list == null || list.getLength() == 0)
				return null;
			List<Catalog> catalogs = new ArrayList<>();
			for (int i = 0; i < list.getLength(); i++) {
				Node node = list.item(i);
				if (node == null)
					continue;
				Catalog catalog = new Catalog();
				catalog.setCode(XmlUtil.getNodeStringValue(node,
						"./up_type_code"));
				catalog.setName(XmlUtil.getNodeStringValue(node,
						"./up_type_name"));
				catalog.setClassTotal(Integer.parseInt(XmlUtil
						.getNodeStringValue(node, "./class_num")));
				catalog.setParent(null);
				NodeList children = XmlUtil.selectNodes(node, "./lesson_type");
				if (children != null && children.getLength() > 0) {
					Set<Catalog> sets = new HashSet<>();
					for (int j = 0; j < children.getLength(); j++) {
						Catalog c = this.buildChildCatalog(children.item(j));
						if (c != null) {
							c.setParent(catalog);
							sets.add(c);
						}
					}
					if (sets.size() > 0) {
						catalog.setChildren(sets);
					}
				}
				catalogs.add(catalog);
			}
			return catalogs;
		} catch (SAXException | ParserConfigurationException
				| XPathExpressionException | IOException e) {
			logger.error(e);
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 构建子课程类型。
	 *
	 * @param node
	 * @return
	 * @throws XPathExpressionException
	 */
	private Catalog buildChildCatalog(Node node)
			throws XPathExpressionException {
		if (node == null)
			return null;
		Catalog catalog = new Catalog();
		catalog.setCode(XmlUtil.getNodeStringValue(node, "./lesson_type_code"));
		catalog.setName(XmlUtil.getNodeStringValue(node, "./lesson_type_name"));
		catalog.setClassTotal(Integer.parseInt(XmlUtil.getNodeStringValue(node,
				"./class_num")));
		NodeList lessonList = XmlUtil.selectNodes(node, ".//lesson");
		if (lessonList != null && lessonList.getLength() > 0) {
			Set<Subject> subjects = new HashSet<>();
			for (int i = 0; i < lessonList.getLength(); i++) {
				Subject subject = this.buildSubject(lessonList.item(i));
				if (subject != null) {
					subject.setCatalog(catalog);
					subjects.add(subject);
				}
			}
			if (subjects.size() > 0) {
				catalog.setSubjects(subjects);
			}
		}
		return catalog;
	}

	/**
	 * 构建课程科目。
	 *
	 * @param node
	 * @return
	 * @throws XPathExpressionException
	 */
	private Subject buildSubject(Node node) throws XPathExpressionException {
		if (node == null)
			return null;
		Subject subject = new Subject();
		subject.setCode(XmlUtil.getNodeStringValue(node, "./lesson_code"));
		//======================================code加e处理
		if(!StringUtils.isEmpty(subject.getCode())) subject.setCode(ID_PREFIX+subject.getCode());
		//======================================
		subject.setName(XmlUtil.getNodeStringValue(node, "./lesson_name"));
		subject.setClassTotal(Integer.parseInt(XmlUtil.getNodeStringValue(node,
				"./class_num")));
		return subject;
	}

	/*
	 * 单个科目中班别的信息集合。
	 *
	 * @see
	 * com.examw.collector.service.IDataServer#loadClasses(java.lang.String,
	 * java.lang.String)
	 */
	@Override
	public List<SubClass> loadClasses(String lesson_type_code,
			String lesson_code) {
		try {
			logger.info("单个科目中班级的信息集合...");
			if (StringUtils.isEmpty(lesson_type_code))
				return null;
			if(StringUtils.isEmpty(lesson_code))
				return loadClasses(lesson_type_code);
			String xml = this.remoteDataProxy.loadLesson(20, lesson_type_code,
					lesson_code, null);
			if (StringUtils.isEmpty(xml))
				return null;
			Document document = XmlUtil.loadDocument(xml);
			Element root = document.getDocumentElement();
			NodeList list = XmlUtil.selectNodes(root, ".//lesson[lesson_code='"	+ lesson_code + "']//class");
			if (list == null || list.getLength() == 0)
				return null;
			List<SubClass> subClasses = new ArrayList<>();
			// =========增加科目===================
			Catalog catalog = new Catalog();
			catalog.setCode(lesson_type_code);
			// ====================================
			// =========增加科目===================
			Subject subject = null;
			if (!StringUtils.isEmpty(lesson_code)) {
				subject = new Subject();
				//===========code 加  e==============
				//subject.setCode(lesson_code);
				subject.setCode(ID_PREFIX+lesson_code);
				//===========code 加  e==============
			}
			// ====================================
			for (int i = 0; i < list.getLength(); i++) {
				if (list.item(i) == null)
					continue;
				SubClass data = new SubClass();
				// =========增加科目=============
				data.setCatalog(catalog);
				data.setSubject(subject);
				// ==============================
				//===========code 加  e==============
				//data.setCode(XmlUtil.getNodeStringValue(list.item(i),"./class_code"));
				data.setCode(ID_PREFIX+XmlUtil.getNodeStringValue(list.item(i),"./class_code"));
				//===========code 加  e==============
				data.setName(XmlUtil.getNodeStringValue(list.item(i),"./class_name"));
				data.setIsLive(Boolean.parseBoolean(XmlUtil.getNodeStringValue(list.item(i), "./is_live")));
				data.setLongDay(Integer.parseInt(XmlUtil.getNodeStringValue(list.item(i), "./long_day")));
				data.setStart(XmlUtil.getNodeStringValue(list.item(i),"./start_date"));
				data.setEnd(XmlUtil.getNodeStringValue(list.item(i),"./end_date"));
				data.setTeacherId(XmlUtil.getNodeStringValue(list.item(i),"./teacher_id"));
				//老师id加e
				if(!"0".equals(data.getTeacherId())){
					data.setTeacherId(ID_PREFIX+data.getTeacherId());
				}
				data.setTeacherName(XmlUtil.getNodeStringValue(list.item(i),"./teacher_name"));
				data.setSourcePrice((int)(Double.parseDouble(XmlUtil.getNodeStringValue(list.item(i), "./source_price"))));
				data.setSalePrice((int)Double.parseDouble(XmlUtil.getNodeStringValue(list.item(i), "./sale_price")));
				data.setTotal(Integer.parseInt(XmlUtil.getNodeStringValue(list.item(i), "./relate_sum")));
				data.setDemo(XmlUtil.getNodeStringValue(list.item(i),"./relate_demo"));
				data.setIsShow(Boolean.parseBoolean(XmlUtil.getNodeStringValue(list.item(i), "./is_show")));
				Node node = XmlUtil.selectSingleNode(list.item(i), "./ad_video");
				if (node != null) {
					AdVideo av = new AdVideo();
					av.setType(Integer.parseInt(XmlUtil.getNodeStringValue(
							node, "./video_type")));
					av.setCode(XmlUtil.getNodeStringValue(node, "./video_code"));
					av.setName(XmlUtil.getNodeStringValue(node, "./video_name"));
					av.setAddress(XmlUtil.getNodeStringValue(node,
							"./video_address"));
					data.setAdVideo(av);
				}
				subClasses.add(data);
			}
			return subClasses;
		} catch (IOException | XPathExpressionException | SAXException
				| ParserConfigurationException e) {
			logger.error(e);
			e.printStackTrace();
		}
		return null;
	}

	// 获取一个类型下所有的班级
	public List<SubClass> loadClasses(String lesson_type_code) {
		try {
			logger.info("整个类别下的班级的信息集合...");
			if (StringUtils.isEmpty(lesson_type_code))
				return null;
			String xml = this.remoteDataProxy.loadLesson(20, lesson_type_code,
					null, null);
			if (StringUtils.isEmpty(xml))
				return null;
			Document document = XmlUtil.loadDocument(xml);
			Element root = document.getDocumentElement();
			NodeList lessonList = XmlUtil.selectNodes(root, ".//lesson");
			if (lessonList == null || lessonList.getLength() == 0)
				return null;
			List<SubClass> subClasses = new ArrayList<>();
			// =========增加科目===================
			Catalog catalog = new Catalog();
			catalog.setCode(lesson_type_code);
			// ====================================
			for (int j = 0; j < lessonList.getLength(); j++) {
				if (lessonList.item(j) == null)
					continue;
				Subject subject = new Subject();
				//================code 加 e======================
				//subject.setCode(XmlUtil.getNodeStringValue(lessonList.item(j),"./lesson_code"));
				subject.setCode(ID_PREFIX+XmlUtil.getNodeStringValue(lessonList.item(j),"./lesson_code"));
				//================code 加 e======================
				subject.setName(XmlUtil.getNodeStringValue(lessonList.item(j),"./lesson_name"));
				NodeList list = XmlUtil.selectNodes(lessonList.item(j),".//class");
				for (int i = 0; i < list.getLength(); i++) {
					if (list.item(i) == null)
						continue;
					SubClass data = new SubClass();
					// =========增加科目=============
					data.setCatalog(catalog);
					data.setSubject(subject);
					// ==============================
					//================code 加 e======================
					//data.setCode(XmlUtil.getNodeStringValue(list.item(i),"./class_code"));
					data.setCode(ID_PREFIX + XmlUtil.getNodeStringValue(list.item(i),"./class_code"));
					//================code 加 e======================
					data.setName(XmlUtil.getNodeStringValue(list.item(i),"./class_name"));
					data.setIsLive(Boolean.parseBoolean(XmlUtil.getNodeStringValue(list.item(i), "./is_live")));
					data.setLongDay(Integer.parseInt(XmlUtil.getNodeStringValue(list.item(i), "./long_day")));
					data.setStart(XmlUtil.getNodeStringValue(list.item(i),"./start_date"));
					data.setEnd(XmlUtil.getNodeStringValue(list.item(i),"./end_date"));
					data.setTeacherId(XmlUtil.getNodeStringValue(list.item(i), "./teacher_id"));
					//teacher_id 加e处理
					//老师id加e
					if(!"0".equals(data.getTeacherId())){
						data.setTeacherId(ID_PREFIX+data.getTeacherId());
					}
					data.setTeacherName(XmlUtil.getNodeStringValue(list.item(i), "./teacher_name"));
					data.setSourcePrice((int)(Double.parseDouble(XmlUtil.getNodeStringValue(list.item(i), "./source_price"))));
					data.setSalePrice((int)(Double.parseDouble(XmlUtil.getNodeStringValue(list.item(i), "./sale_price"))));
					data.setTotal(Integer.parseInt(XmlUtil.getNodeStringValue(list.item(i), "./relate_sum")));
					data.setDemo(XmlUtil.getNodeStringValue(list.item(i),"./relate_demo"));
					data.setIsShow(Boolean.parseBoolean(XmlUtil.getNodeStringValue(list.item(i), "./is_show")));
					Node node = XmlUtil.selectSingleNode(list.item(i),"./ad_video");
					if (node != null) {
						AdVideo av = new AdVideo();
						av.setType(Integer.parseInt(XmlUtil.getNodeStringValue(node, "./video_type")));
						av.setCode(XmlUtil.getNodeStringValue(node,"./video_code"));
						av.setName(XmlUtil.getNodeStringValue(node,"./video_name"));
						av.setAddress(XmlUtil.getNodeStringValue(node,"./video_address"));
						data.setAdVideo(av);
					}
					subClasses.add(data);
				}
			}
			return subClasses;
		} catch (IOException | XPathExpressionException | SAXException
				| ParserConfigurationException e) {
			logger.error(e);
			e.printStackTrace();
		}
		return null;
	}

	/*
	 * 获取课时(讲)集合。
	 *
	 * @see
	 * com.examw.collector.service.IDataServer#loadRelates(java.lang.String)
	 */
	@Override
	public List<Relate> loadRelates(String class_code) {
		try {
			logger.info("获取课时(讲)集合...");
			if (StringUtils.isEmpty(class_code))
				return null;
			String xml = this.remoteDataProxy.loadLesson(3, null, null,
					class_code.replace(ID_PREFIX, ""));	//code做减e处理
			if (StringUtils.isEmpty(xml))
				return null;
			Document document = XmlUtil.loadDocument(xml);
			Element root = document.getDocumentElement();
			NodeList list = XmlUtil.selectNodes(root, "//relate");
			if (list == null || list.getLength() == 0)
				return null;
			List<Relate> relates = new ArrayList<>();
			// ==================================================
			SubClass grade = new SubClass();
			//================code 加 e======================
			//grade.setCode(class_code);
			grade.setCode(class_code);	//class_code本身带有e
			//================code 加 e======================
			// ==================================================
			for (int i = 0; i < list.getLength(); i++) {
				if (list.item(i) == null)
					continue;
				Relate data = new Relate();
				// ==================================================
				data.setSubclass(grade);
				// ==================================================
				data.setNum(Integer.parseInt(XmlUtil.getNodeStringValue(
						list.item(i), "./relate_num")));
				data.setName(XmlUtil.getNodeStringValue(list.item(i),
						"./relate_name"));
				data.setIsDemo((XmlUtil.getNodeStringValue(list.item(i),
						"./relate_demo")).equalsIgnoreCase("1"));
				data.setUpdateDate(XmlUtil.getNodeStringValue(list.item(i),
						"./update_date"));
				data.setIsNew((XmlUtil.getNodeStringValue(list.item(i),
						"./relate_demo")).equalsIgnoreCase("1"));
				data.setAddress(XmlUtil.getNodeStringValue(list.item(i),
						"./listen_address"));
				relates.add(data);
			}
			return relates;
		} catch (IOException | SAXException | ParserConfigurationException
				| XPathExpressionException e) {
			logger.error(e);
			e.printStackTrace();
		}
		return null;
	}

	/*
	 * 获取单个科目中套餐的信息接口。
	 *
	 * @see com.examw.collector.service.IDataServer#loadPacks(java.lang.String,
	 * java.lang.String)
	 */
	@Override
	public List<Pack> loadPacks(String lesson_type_code, String lesson_code) {
		try {
			logger.info("获取单个科目中套餐的信息接口...	lesson_code非必需[修改了下]");
			if (StringUtils.isEmpty(lesson_type_code))
				return null;
			if (StringUtils.isEmpty(lesson_code)) // 取全科套餐
				return loadPacks(lesson_type_code);
			String xml = this.remoteDataProxy.loadLesson(4, lesson_type_code,
					lesson_code, null);
			if (StringUtils.isEmpty(xml))
				return null;
			Document document = XmlUtil.loadDocument(xml);
			Element root = document.getDocumentElement();
			NodeList list = XmlUtil.selectNodes(root, ".//lesson[lesson_code='"
						+ lesson_code + "']//discount");
			if (list == null || list.getLength() == 0)
				return null;
			List<Pack> packs = new ArrayList<>();
			// ==================================================
			Catalog catalog = new Catalog();
			catalog.setCode(lesson_type_code);
			// ==================================================
			// ==================================================
			Subject subject = null;
			if (!StringUtils.isEmpty(lesson_code)) {
				subject = new Subject();
				//================code 加 e======================
				//subject.setCode(lesson_code);
				subject.setCode(ID_PREFIX+lesson_code);
				//================code 加 e======================
			}
			// ==================================================
			for (int i = 0; i < list.getLength(); i++) {
				if (list.item(i) == null)
					continue;
				Pack data = new Pack();
				// ==================================================
				data.setCatalog(catalog);
				data.setSubject(subject);
				// ==================================================
				//================code 加 e======================
//				data.setCode(XmlUtil.getNodeStringValue(list.item(i),"./discount_code"));
				data.setCode(ID_PREFIX + XmlUtil.getNodeStringValue(list.item(i),"./discount_code"));
				//================code 加 e======================
				data.setName(XmlUtil.getNodeStringValue(list.item(i),
						"./discount_name"));
				data.setSource((int)(Double.parseDouble(XmlUtil.getNodeStringValue(
						list.item(i), "./source_amount"))));
				data.setDiscount((int)(Double.parseDouble(XmlUtil.getNodeStringValue(
						list.item(i), "./discount_amount"))));
				data.setStudentPrice((int)(Double.parseDouble(XmlUtil.getNodeStringValue(
						list.item(i), "./old_user_price"))));	//设置老学员价
				data.setIsShow(Boolean.parseBoolean(XmlUtil.getNodeStringValue(
						list.item(i), "./is_show")));
				// data.setClassCodes(XmlUtil.getNodeStringValue(list.item(i),
				// "./class_code").split(","));
				//===============这里进行了 加e处理 ======================
				data.setClassCodes(classCodesAdd_e(XmlUtil.getNodeStringValue(list.item(i),
						"./class_code")));
				packs.add(data);
			}
			return packs;
		} catch (IOException | SAXException | ParserConfigurationException
				| XPathExpressionException e) {
			logger.error(e);
			e.printStackTrace();
		}
		return null;
	}
	//获取整个类别下套餐的信息接口...
	public List<Pack> loadPacks(String lesson_type_code){
		try{
			logger.info("获取整个类别下套餐的信息接口...");
			String xml = this.remoteDataProxy.loadLesson(4, lesson_type_code,null, null);
			if (StringUtils.isEmpty(xml))
				return null;
			Document document = XmlUtil.loadDocument(xml);
			Element root = document.getDocumentElement();
			//获取整个大类下的所有套餐
			NodeList lessonList = XmlUtil.selectNodes(root, ".//lesson");
			if (lessonList == null || lessonList.getLength() == 0)
				return null;
			List<Pack> packs = new ArrayList<>();
			// ==================================================
			Catalog catalog = new Catalog();
			catalog.setCode(lesson_type_code);
			// ==================================================
			for (int j = 0; j < lessonList.getLength(); j++) {
				if (lessonList.item(j) == null)
					continue;
				Subject subject = new Subject();
				//================code 加 e======================
//				subject.setCode(XmlUtil.getNodeStringValue(lessonList.item(j),"./lesson_code"));
				subject.setCode(ID_PREFIX+XmlUtil.getNodeStringValue(lessonList.item(j),"./lesson_code"));
				//================code 加 e======================
				subject.setName(XmlUtil.getNodeStringValue(lessonList.item(j),"./lesson_name"));
				//如果lesson_code == 子类别的ID表示全科套餐
				if(lesson_type_code.equals(subject.getCode().replace(ID_PREFIX, ""))){//这里要做减e处理
					subject = null;
				}
				//其下的套餐
				NodeList list = XmlUtil.selectNodes(lessonList.item(j),".//discount");
				for (int i = 0; i < list.getLength(); i++) {
					if (list.item(i) == null)
						continue;
					Pack data = new Pack();
					// ==================================================
					data.setCatalog(catalog);
					data.setSubject(subject);
					// ==================================================
					//===================code 加 e================================
//					data.setCode(XmlUtil.getNodeStringValue(list.item(i),"./discount_code"));
					data.setCode(ID_PREFIX+XmlUtil.getNodeStringValue(list.item(i),"./discount_code"));
					//===================code 加 e================================
					data.setName(XmlUtil.getNodeStringValue(list.item(i),
							"./discount_name"));
					data.setSource((int)(Double.parseDouble(XmlUtil.getNodeStringValue(
							list.item(i), "./source_amount"))));
					data.setDiscount((int)(Double.parseDouble(XmlUtil.getNodeStringValue(
							list.item(i), "./discount_amount"))));
					data.setStudentPrice((int)(Double.parseDouble(XmlUtil.getNodeStringValue(
							list.item(i), "./old_user_price"))));	//设置老学员价
					data.setIsShow(Boolean.parseBoolean(XmlUtil.getNodeStringValue(
							list.item(i), "./is_show")));
					// data.setClassCodes(XmlUtil.getNodeStringValue(list.item(i),
					// "./class_code").split(","));
					//===============这里进行了 加e处理 ======================
					data.setClassCodes(classCodesAdd_e(XmlUtil.getNodeStringValue(list.item(i),
							"./class_code")));
					packs.add(data);
				}
			}
			return packs;
		}catch (IOException | SAXException | ParserConfigurationException
				| XPathExpressionException e) {
			logger.error(e);
			e.printStackTrace();
		}
		return null;
	}

	/*
	 * 注册。
	 *
	 * @see com.examw.collector.service.IDataServer#register(java.lang.String,
	 * java.lang.String, java.lang.String, java.lang.String, java.lang.String,
	 * java.lang.String)
	 */
	@Override
	public boolean register(String userName, String password, String name,
			String email, String province, String tel) throws Exception {
		logger.info("注册...");
		if (StringUtils.isEmpty(userName))
			throw new Exception("注册的用户名不能为空!");
		if (StringUtils.isEmpty(password))
			throw new Exception("注册的密码不能为空!");
		if (StringUtils.isEmpty(name))
			throw new Exception("姓名不能为空!");
		String xml = this.remoteDataProxy.postUsers(1, null, null, null, null,
				null, null, null, userName, password, name, email, province,
				tel);
		if (StringUtils.isEmpty(xml))
			throw new Exception("未获取服务端反馈!");
		Integer result = this.checkCallbackCode(xml);
		String err = null;
		if (result == -1) {
			logger.info(err = "反馈数据校验失败!");
			throw new Exception(err);
		}
		if (result == 2) {
			logger.info(err = "用户已存在!");
			throw new Exception(err);
		}
		if (result == 3) {
			logger.info(err = "用户名、密码字符串不合法!");
			throw new Exception(err);
		}
		if (result == 4) {
			logger.info(err = "失败!");
			return false;
		}
		return result == 1;
	}

	/*
	 * 用户登录。
	 *
	 * @see com.examw.collector.service.IDataServer#login(java.lang.String,
	 * java.lang.String)
	 */
	@Override
	public boolean login(String userName, String password) throws Exception {
		if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password))
			throw new Exception("用户名或密码不能为空!");
		String xml = this.remoteDataProxy.postUsers(2, null, null, null, null,
				null, null, null, userName, password, null, null, null, null);
		if (StringUtils.isEmpty(xml))
			throw new Exception("未获取服务端反馈!");
		Integer result = this.checkCallbackCode(xml);
		String err = null;
		if (result == 2) {
			logger.info(err = "用户名、密码错误!");
			throw new Exception(err);
		}
		if (result == 3) {
			logger.info(err = "用户已禁用!");
			throw new Exception(err);
		}
		if (result == 4) {
			logger.info(err = "失败!");
			return false;
		}
		return result == 1;
	}

	/*
	 * 用户选课。
	 *
	 * @see
	 * com.examw.collector.service.IDataServer#choiceCourse(java.lang.String,
	 * java.lang.String, java.lang.Integer, java.lang.String[])
	 */
	@Override
	public boolean choiceCourse(String userName, String password, Integer type,
			String[] codes) throws Exception {
		if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password))
			throw new Exception("用户名或密码不能为空!");
		String[] lesson_code = (type == 1) ? codes : null, discount_code = (type == 2) ? codes
				: null;
		String xml = this.remoteDataProxy.postUsers(3, null, lesson_code,
				discount_code, null, null, null, null, userName, password,
				null, null, null, null);
		if (StringUtils.isEmpty(xml))
			throw new Exception("未获取服务端反馈!");
		Integer result = this.checkCallbackCode(xml);
		String err = null;
		if (result == 2) {
			logger.info(err = "用户名、密码错误!");
			throw new Exception(err);
		}
		if (result == 3) {
			logger.info(err = "用户已禁用!");
			throw new Exception(err);
		}
		if (result == 4) {
			logger.info(err = "课程已过期!");
			throw new Exception(err);
		}
		if (result == 5) {
			logger.info(err = "套餐已过期!");
			throw new Exception(err);
		}
		if (result == 6) {
			logger.info(err = "课程或套餐参数不正确!");
			throw new Exception(err);
		}
		if (result == 7) {
			logger.info(err = "失败!");
			return false;
		}
		if (result == 9) {
			logger.info(err = "重复选课!");
			throw new Exception(err);
		}
		if (result == 99) {
			logger.info(err = "不能重复提交订单请求!");
			throw new Exception(err);
		}
		return result == 1;
	}

	/*
	 * 充值。
	 *
	 * @see com.examw.collector.service.IDataServer#recharge(java.lang.String,
	 * java.lang.String, java.lang.Integer, java.lang.String, java.lang.String)
	 */
	@Override
	public boolean recharge(String userName, String password, Integer money,
			String cardnum, String cardpwd) throws Exception {
		if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password))
			throw new Exception("用户名或密码不能为空!");
		if (StringUtils.isEmpty(cardnum) || StringUtils.isEmpty(cardpwd))
			throw new Exception("学习卡或密码为不能为空!");
		String xml = this.remoteDataProxy.postUsers(4, null, null, null, null,
				money.toString(), cardnum, cardpwd, userName, password, null,
				null, null, null);
		if (StringUtils.isEmpty(xml))
			throw new Exception("未获取服务端反馈!");
		Integer result = this.checkCallbackCode(xml);
		String err = null;
		if (result == 2) {
			logger.info(err = "用户名、密码错误!");
			throw new Exception(err);
		}
		if (result == 3) {
			logger.info(err = "学习卡未激活或已冻结!");
			throw new Exception(err);
		}
		if (result == 4) {
			logger.info(err = "学习卡已过期!");
			throw new Exception(err);
		}
		if (result == 5) {
			logger.info(err = "学习卡余额不足!");
			throw new Exception(err);
		}
		if (result == 6) {
			logger.info(err = "用户名、密码错误!");
			throw new Exception(err);
		}
		if (result == 7) {
			logger.info(err = "学习卡账号或密码校验失败!");
			throw new Exception(err);
		}
		if (result == 8) {
			logger.info(err = "失败!");
			return false;
		}
		if (result == 9) {
			logger.info(err = "用户已经使用过优惠卡充值!");
			throw new Exception(err);
		}
		return result == 1;
	}

	/*
	 * 扣费。
	 *
	 * @see com.examw.collector.service.IDataServer#payment(java.lang.String,
	 * java.lang.String, java.lang.String, java.lang.Integer)
	 */
	@Override
	public boolean payment(String userName, String password, String orderCode,
			Integer money) throws Exception {
		if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password))
			throw new Exception("用户名或密码不能为空!");
		String xml = this.remoteDataProxy.postUsers(5, orderCode, null, null,
				null, money.toString(), null, null, userName, password, null,
				null, null, null);
		if (StringUtils.isEmpty(xml))
			throw new Exception("未获取服务端反馈!");
		Integer result = this.checkCallbackCode(xml);
		String err = null;
		if (result == 2) {
			logger.info(err = "定单号不存在!");
			throw new Exception(err);
		}
		if (result == 3) {
			logger.info(err = "用户余额不足!");
			throw new Exception(err);
		}
		if (result == 4) {
			logger.info(err = "扣款的金额不正确!");
			throw new Exception(err);
		}
		if (result == 5) {
			logger.info(err = "此订单已支付!");
			throw new Exception(err);
		}
		if (result == 6) {
			logger.info(err = "用户名、密码错误!");
			throw new Exception(err);
		}
		if (result == 7) {
			logger.info(err = "订单状态转化失败!");
			throw new Exception(err);
		}
		if (result == 8) {
			logger.info("失败!");
			return false;
		}
		return result == 1;
	}

	/*
	 * 学习。
	 *
	 * @see com.examw.collector.service.IDataServer#study(java.lang.String,
	 * java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public boolean study(String userName, String password, String lesson_code,
			String part) throws Exception {
		if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password))
			throw new Exception("用户名或密码不能为空!");
		String xml = this.remoteDataProxy.postUsers(6, null,
				new String[] { lesson_code }, null, part, null, null, null,
				null, password, null, null, null, null);
		if (StringUtils.isEmpty(xml))
			throw new Exception("未获取服务端反馈!");
		Integer result = this.checkCallbackCode(xml);
		String err = null;
		if (result == 2) {
			logger.info(err = "用户名、密码错误!");
			throw new Exception(err);
		}
		if (result == 3) {
			logger.info(err = "课程状态异常!");
			throw new Exception(err);
		}
		if (result == 4) {
			logger.info(err = "课程参数信息有误!");
			throw new Exception(err);
		}
		if (result == 5) {
			logger.info(err = "失败!");
			return false;
		}
		return result == 1;
	}

	/**
	 * 校验反馈数据。
	 *
	 * @param xml
	 * @return 状态参数。
	 */
	private Integer checkCallbackCode(String xml) {
		try {
			Document document = XmlUtil.loadDocument(xml);
			Element root = document.getDocumentElement();
			String state = XmlUtil.getNodeStringValue(root, "./state");
			String checkCode = this.remoteDataProxy
					.buildCheckCode(new String[] {
							XmlUtil.getNodeStringValue(root, "./cataid"),
							XmlUtil.getNodeStringValue(root, "./ordercode"),
							XmlUtil.getNodeStringValue(root, "./money"),
							XmlUtil.getNodeStringValue(root, "./username"),
							XmlUtil.getNodeStringValue(root, "./password"),
							XmlUtil.getNodeStringValue(root, "./time"),
							XmlUtil.getNodeStringValue(root, "./playurl"),
							state });
			if (checkCode.equalsIgnoreCase(XmlUtil.getNodeStringValue(root,
					"./md5Str"))) {
				return Integer.parseInt(state);
			}
		} catch (SAXException | IOException | ParserConfigurationException
				| XPathExpressionException e) {
			logger.error(e);
			e.printStackTrace();
		}
		return -1;
	}

	/*
	 * 定制大屏播放器.
	 *
	 * @see com.examw.collector.service.IDataServer#loadVideo(java.lang.String,
	 * java.lang.Integer, java.lang.String)
	 */
	@Override
	public String loadVideo(String userName, Integer type, String videoId)
			throws Exception {
		if (StringUtils.isEmpty(userName))
			throw new Exception("用户名不能为空!");
		if (type == null)
			type = 3;
		return this.remoteDataProxy.loadVideo(videoId, type, userName);
	}

	@Override
	public TeacherEntity loadTeacher(String id) {
		if(StringUtils.isEmpty(id) || id.equals("0") || id.equals("e0") || id.equals(ID_PREFIX)||id.equals("enull"))
			return null;
		TeacherEntity teacher = null;
		try
		{
			teacher = new TeacherEntity();
			//==================老师id 加e ===============================
			teacher.setId(id);
			//==================老师id 加e ===============================
			String html = this.remoteDataProxy.loadTeacher(id.replace(ID_PREFIX, "")); //去e处理
			html = html.replaceAll("\\s", "");
			Pattern teacherName = Pattern.compile("<h2><dl><dt>(.+)</dt><ddclass=\"ms_itdt_dd1\"(.+)");
			Pattern teacherLessons = Pattern.compile("<dt><strong>(.+)</strong></dt><ddstyle=(.*)><fonttitle=(.*)>(.*)</font></dd></dl>");//</dt><ddstyle=(.+)<fonttile=(.+)>(.+)</font></dd></dl>");
			Pattern teacherInfo = Pattern.compile("<ul><li><pclass=\"dd_p_1center\">(.+)</p></li></ul>");
			Pattern teacherImg = Pattern.compile("<imgsrc=\"([/\\w\\d.]+)\"width=\"479px");
			Matcher m2 = teacherName.matcher(html);//group(1);
			while(m2.find()){
				String s = m2.group(1);
				if(s!=null){
					teacher.setName(s.replace("老师", ""));
					break;
				}
			}
		    m2 = teacherLessons.matcher(html); //group(4);
		    while(m2.find()){
				String s = m2.group(4);
				if(s!=null){
					teacher.setLessons(s);
					break;
				}
			}
		    m2 = teacherInfo.matcher(html);	//group(1);
		    while(m2.find()){
				String s = m2.group(1);
				if(s!=null){
					teacher.setDescription(s);
					teacher.setInfo(s);
					break;
				}
			}
		    m2 = teacherImg.matcher(html);	//group(1);
		    while(m2.find()){
				String s = m2.group(1);
				if(s!=null){
					if(StringUtils.isEmpty(savePath)){
						teacher.setImgurl("http://www.edu24ol.com"+s);
					}else{
						teacher.setImgurl(getImagePath("http://www.edu24ol.com"+s));
					}
					break;
				}
			}
		    teacher.setAddDate(new Date());
		}catch(Exception e)
		{
			e.printStackTrace();
		}
		return teacher;
	}
	private String getImagePath(String url){
		String path = url;
		String fileName = url.substring(url.lastIndexOf("/")+1);
		//如果使用默认图片,地址有点不同
		if(fileName.equalsIgnoreCase("tc_w_b.gif"))
		{
			path = url = "http://www.edu24ol.com/web_teacher/images2011/tc_w_b.gif";
		}
		String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
		String filePath = null;
		if(!savePath.endsWith("/")){
			filePath = savePath + File.separator + date;
		}else{
			filePath = savePath + date;
		}
		if(!new File(filePath).exists()){
			new File(filePath).mkdirs();
		}
		try{
			java.net.HttpURLConnection conn =(java.net.HttpURLConnection) new URL(url).openConnection();
            if (conn.getResponseCode() == 200) {

                java.io.InputStream is = (java.io.InputStream) conn.getInputStream();
                try{
                    String realPath = filePath+File.separator+fileName;
                    FileOutputStream baos = new FileOutputStream(new File(realPath));
                    int buffer = 1024;
                    byte[] b = new byte[buffer];
                    int n = 0;
                    while ((n = is.read(b, 0, buffer)) > 0) {
                        baos.write(b, 0, n);
                    }
                    is.close();
                    baos.close();
                }catch(Exception e){
                	e.printStackTrace();
                }
                path = "/"+date+"/"+fileName;
            }else{
            }
        }catch(Exception e){
                e.printStackTrace();
        }
		return path;
	}
	/**
	 * classCode中的ID里加e
	 * @return
	 */
	private String classCodesAdd_e(String classCodes){
		StringBuilder result = new StringBuilder();
		if(!StringUtils.isEmpty(classCodes)){
			String[] codes = classCodes.split(",");
			for(String code : codes)
			{
				result.append(ID_PREFIX+code).append(",");
			}
		}
		if(result.length()>0)
			return result.substring(0, result.length()-1);	//去最后一个逗号
		return null;
	}

	/**
	 * 采集页面的班级和套餐ID
	 */
	@Override
	public Map<String, String> loadPageIds(String url) {
		if(StringUtils.isEmpty(url)) return null;
		try{
			String html = HttpUtil.sendRequest(url, "GET", null,"GBK");	//获取页面源代码
			html = html.replaceAll("<!--([\\w\\W](?<!-->))*-->", "");	//替换中间的注释
			html = html.toUpperCase();	//全部转大写
			//匹配所有的INPUT
			Pattern input = Pattern.compile("([\\w\\W&&[^<]&&[^>]]*)<INPUT([\\w\\W&&[^<]&&[^>]]+)/?>([\\w\\W&&[^<]]*)");
			Matcher m2 = input.matcher(html);
			//找出所有的INPUT
			List<String> inputList = new ArrayList<String>();
			StringBuffer gradeIds = new StringBuffer();
			StringBuffer packIds = new StringBuffer();
			while(m2.find()){
				inputList.add(m2.group(2));
			}
			//循环INPUT,找出对应的DISCOUNTIDS和IDS的值	同时加上前缀e
			for(String s:inputList){
				if(s.contains("DISCOUNTIDS")){
					packIds.append(ID_PREFIX).append(s.replaceAll("([\\w\\W]+)VALUE\\s?=\\s?\"?([\\d]+)([\\w\\W]+)", "$2")).append(",");
				}
				else if(s.contains("IDS")){
					gradeIds.append(ID_PREFIX).append(s.replaceAll("([\\w\\W]+)VALUE\\s?=\\s?\"?([\\d]+)([\\w\\W]+)", "$2")).append(",");
				}
			}
			Map<String,String> map = new HashMap<String,String>();
			if(packIds.length()>0)
				map.put("PACKIDS", ","+packIds.toString());	//类似 ,111,112,这种形式
			if(gradeIds.length()>0)
				map.put("GRADEIDS", ","+gradeIds.toString());
			return map;
		}catch(Exception e){
			e.printStackTrace();
		}
		return null;
	}
	@Override
	public String loadPageGradeIds(String url) {
//		Map<String, String> map = this.loadPageIds(url);
//		if(map == null)	return null;
//		return map.get("GRADEIDS");
		return this.loadPageIds(url, "GRADEIDS");
	}
	@Override
	public String loadPagePackIds(String url) {
//		Map<String, String> map = this.loadPageIds(url);
//		if(map == null)	return null;
//		return map.get("PACKIDS");
		return this.loadPageIds(url, "PACKIDS");
	}
	//使用JSOUP匹配
	private String loadPageIds(String url,String flag){
		if(StringUtils.isEmpty(url)) return null;
		try{
			String html = HttpUtil.sendRequest(url, "GET", null,"GBK");	//获取页面源代码
			html = html.replaceAll("<!--([\\w\\W](?<!-->))*-->", "");	//替换中间的注释
			html = html.toUpperCase();	//全部转大写
			org.jsoup.nodes.Document dom = Jsoup.parse(html);
			//Document dom = Jsoup.connect("http://www.edu24ol.com/classList_jzs1.asp").get();
			//去掉display:none的情况
			org.jsoup.select.Elements elements = dom.select("div[style~=((?i)display:none(?i))]");
			for(org.jsoup.nodes.Element e:elements){
				e.html("");
			}
			org.jsoup.select.Elements  list = null;
			if("PACKIDS".equals(flag))
			{
				list = dom.select("INPUT[NAME=DISCOUNTIDS],INPUT#DISCOUNTIDS");
			}else if("GRADEIDS".equals(flag))
			{
				list = dom.select("INPUT[NAME=IDS],INPUT#IDS");
			}
			if(list == null || list.size() == 0) return null;
			StringBuffer ids = new StringBuffer();
			for(org.jsoup.nodes.Element g : list){
				ids.append(ID_PREFIX).append(g.attr("value").trim()).append(",");
			}
			return ","+ids.toString();
		}catch(Exception e){
			e.printStackTrace();
			return null;
		}
	}
}


text:
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.scavi.brainsqueeze.puzzle;

import org.junit.Assert;
import org.junit.Test;

/**
 * @author Michael Heymel
 * @since 15/09/16
 */
public class ArrayPuzzleTest {

    @Test
    public void testArrayPuzzle() {
        ArrayPuzzle puzzle = new ArrayPuzzle();
        int[] input = {3, 5, 2, 4};
        int[] expOutput = {40, 24, 60, 30};
        int[] result = puzzle.determineArrayProductExceptCurrentPos(input);
        Assert.assertArrayEquals(expOutput, result);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testNullInput() {
        ArrayPuzzle puzzle = new ArrayPuzzle();
        puzzle.determineArrayProductExceptCurrentPos(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void test1EntryInput() {
        ArrayPuzzle puzzle = new ArrayPuzzle();
        int[] input = {42};
        puzzle.determineArrayProductExceptCurrentPos(input);
    }

    @Test
    public void test1MinSubarraySize() {
        int[] input = new int[]{12, 1, 5, 7, 1, 2, 3};
        ArrayPuzzle puzzle = new ArrayPuzzle();
        int min = puzzle.findMinimumOfSubarray(input, 3);
        Assert.assertEquals(6, min);
    }

    @Test
    public void test2MinSubarraySize() {
        int[] input = {42};
        ArrayPuzzle puzzle = new ArrayPuzzle();
        int min = puzzle.findMinimumOfSubarray(input, 1);
        Assert.assertEquals(42, min);
    }

    @Test(expected = IllegalArgumentException.class)
    public void test3MinSubarraySize() {
        int[] input = {42};
        ArrayPuzzle puzzle = new ArrayPuzzle();
        puzzle.findMinimumOfSubarray(input, 2);
    }

    @Test
    public void testSwitchArray1() {
        int[] input = new int[]{3, 2, 0, 1};
        int[] expectedOutput = new int[]{1, 0, 3, 2};
        ArrayPuzzle puzzle = new ArrayPuzzle();
        int[] output = puzzle.switchArray(input);
        Assert.assertArrayEquals(expectedOutput, output);
    }

    @Test
    public void testSwitchArray2() {
        int[] input = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8};
        int[] expectedOutput = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8};
        ArrayPuzzle puzzle = new ArrayPuzzle();
        int[] output = puzzle.switchArray(input);
        Assert.assertArrayEquals(expectedOutput, output);
    }

    @Test
    public void testSwitchArray3() {
        int[] input = new int[]{4, 1, 2, 0, 3};
        int[] expectedOutput = new int[]{3, 1, 2, 4, 0};
        ArrayPuzzle puzzle = new ArrayPuzzle();
        int[] output = puzzle.switchArray(input);
        Assert.assertArrayEquals(expectedOutput, output);
    }
}


text: text:"This is the body of the a tag"


text:
using System.Collections.Generic;
using Tellurium.VisualAssertions.Screenshots.Domain;

namespace Tellurium.VisualAssertion.Dashboard.Models.Home
{
    public class SaveGlobalBlindRegionsDTO
    {
        public string BrowserName { get; set; }
        public List<BlindRegion> BlindRegions { get; set; }
        public long TestCaseId { get; set; }

        public SaveGlobalBlindRegionsDTO()
        {
            BlindRegions = new List<BlindRegion>();
        }
    }
}


text:
function zi = natural_neighbour(xx, yy, zz, xi, yi)
% Perform interpolation using the natural neighbour method.
% zi = natural_neighbour(xx, yy, zz, xi, yi)
%
%	Perform natural neighbour interpolation using code from
% http://www.marine.csiro.au/~sak007/
%
% Arguments:
%   xx, yy, zz = scattered input data
%   xi, yi     = output positions
%
% See also:
%   griddata, adaptiveNC, rbf, kriging
%
% Mex gateway by Matt P. Foster <ee1mpf@bath.ac.uk>

error(nargchk(5, 5, nargin));
zi = mex_nn(xx, yy, zz, xi, yi);

end % function


text:
from superironic import executable

def main():
    return executable.run_superironic_keyring()


text:
package com.hippo.nimingban.client;

public class Notice {

    public boolean enable;
    public long date;
    public String content;
}


text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Jukebox.Core
{
    public interface IDisplay
    {
        int Columns { get; }
        int Rows { get; }
    }
}


text:
module SurrogatorUploader
  VERSION = "0.9.0"
end


text:
import { expect } from 'chai';
import { describe, it } from 'mocha';

import { DirectiveLocation } from '../../language/directiveLocation.js';

import { GraphQLDirective } from '../directives.js';
import { GraphQLInt, GraphQLString } from '../scalars.js';

describe('Type System: Directive', () => {
  it('defines a directive with no args', () => {
    const directive = new GraphQLDirective({
      name: 'Foo',
      locations: [DirectiveLocation.QUERY],
    });

    expect(directive).to.deep.include({
      name: 'Foo',
      args: [],
      isRepeatable: false,
      locations: ['QUERY'],
    });
  });

  it('defines a directive with multiple args', () => {
    const directive = new GraphQLDirective({
      name: 'Foo',
      args: {
        foo: { type: GraphQLString },
        bar: { type: GraphQLInt },
      },
      locations: [DirectiveLocation.QUERY],
    });

    expect(directive).to.deep.include({
      name: 'Foo',
      args: [
        {
          name: 'foo',
          description: undefined,
          type: GraphQLString,
          defaultValue: undefined,
          deprecationReason: undefined,
          extensions: {},
          astNode: undefined,
        },
        {
          name: 'bar',
          description: undefined,
          type: GraphQLInt,
          defaultValue: undefined,
          deprecationReason: undefined,
          extensions: {},
          astNode: undefined,
        },
      ],
      isRepeatable: false,
      locations: ['QUERY'],
    });
  });

  it('defines a repeatable directive', () => {
    const directive = new GraphQLDirective({
      name: 'Foo',
      isRepeatable: true,
      locations: [DirectiveLocation.QUERY],
    });

    expect(directive).to.deep.include({
      name: 'Foo',
      args: [],
      isRepeatable: true,
      locations: ['QUERY'],
    });
  });

  it('can be stringified, JSON.stringified and Object.toStringified', () => {
    const directive = new GraphQLDirective({
      name: 'Foo',
      locations: [DirectiveLocation.QUERY],
    });

    expect(String(directive)).to.equal('@Foo');
    expect(JSON.stringify(directive)).to.equal('"@Foo"');
    expect(Object.prototype.toString.call(directive)).to.equal(
      '[object GraphQLDirective]',
    );
  });

  it('rejects a directive with invalid name', () => {
    expect(
      () =>
        new GraphQLDirective({
          name: 'bad-name',
          locations: [DirectiveLocation.QUERY],
        }),
    ).to.throw('Names must only contain [_a-zA-Z0-9] but "bad-name" does not.');
  });

  it('rejects a directive with incorrectly named arg', () => {
    expect(
      () =>
        new GraphQLDirective({
          name: 'Foo',
          locations: [DirectiveLocation.QUERY],
          args: {
            'bad-name': { type: GraphQLString },
          },
        }),
    ).to.throw('Names must only contain [_a-zA-Z0-9] but "bad-name" does not.');
  });
});


text:
package de.haw.vs.enchiridion.connectionhandler;

import java.io.IOException;
import java.io.InputStream;

/**
 * Interface handling incoming Client requests
 */
public interface IClientRequestHandler extends Runnable {

    /**
     * Process the incoming request message
     *
     * @param in InputStream of incoming request
     */
    public void handleIncomingRequest(InputStream in) throws IOException;
}


text:
from neutron.api import extensions
from neutron.common import constants

class Subnetallocation(extensions.ExtensionDescriptor):
    """Extension class supporting subnet allocation."""

    @classmethod
    def get_name(cls):
        return "Subnet Allocation"

    @classmethod
    def get_alias(cls):
        return constants.SUBNET_ALLOCATION_EXT_ALIAS

    @classmethod
    def get_description(cls):
        return "Enables allocation of subnets from a subnet pool"

    @classmethod
    def get_namespace(cls):
        return ("http://docs.openstack.org/ext/"
                "%s/api/v1.0" % constants.SUBNET_ALLOCATION_EXT_ALIAS)

    @classmethod
    def get_updated(cls):
        return "2015-03-30T10:00:00-00:00"

    def get_required_extensions(self):
        return ["router"]

    @classmethod
    def get_resources(cls):
        """Returns Ext Resources."""
        return []

    def get_extended_resources(self, version):
        return {}


text:
package com.raulh82vlc.MoviesFinder.ui.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
 * Proportional view which enables a 20% increased view height compared with the width

 * @author Raul Hernandez Lopez
 * on 30/04/2016.
 */
public class ProportionalImageView extends ImageView {

    private static final double PERCENTAGE_TO_INCREASE = 1.2;

    public ProportionalImageView(Context context) {
        super(context);
    }

    public ProportionalImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ProportionalImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getMeasuredWidth();
        int height = (int) (width * PERCENTAGE_TO_INCREASE);
        setMeasuredDimension(width, height);
    }
}


text:
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;

/**
 * Class which handles the implementation of the translation.
 *
 * @author Andrew Mass
 * @version 1.0.1
 */
public class Translator {

  private final Display display;

  private File inputFile;
  private File outputFile;

  private Map<String, Message> messageData;

  private ArrayList<String> axis;
  private ArrayList<float[]> values;

  private int convertProgress;
  private int normalizeProgress;
  private int printProgress;

  /**
   * Standard constructor for the Translator class. Creates a new display and runs initMessageInfo()
   */
  public Translator() {
    display = new Display(this);
    display.setVisible(true);
    if(!initMessageInfo()) System.exit(0);
  }

  /**
   * Looks for the configuration file. If found, constructs a series of Message objects each with an array of SubMessage
   * objects. Populates instance variables based on data from the configuration file.
   *
   * @return Whether the initialization was successful or unsuccessful.
   */
  public boolean initMessageInfo() {
    File configFile = new File(new File("").getAbsolutePath() + "/config.txt");
    if(configFile == null || !configFile.canRead() || !configFile.exists() || configFile.isDirectory() ||
        !configFile.isFile()) {
      display
          .error("Configuration File Error",
                 "The configuration file cannot be read or found.\n\nPlease place it in the same directory as the program and restart the program.");
      return false;
    }

    Scanner reader;
    try {
      reader = new Scanner(configFile);
    } catch(FileNotFoundException e) {
      display
          .error("Configuration File Error",
                 "The configuration file cannot be read or found.\n\nPlease place it in the same directory as the program and restart the program.");
      return false;
    }

    messageData = new HashMap<String, Message>();

    /*
     * This loops through each line of the configuration file and constructs Message objects out of the information
     * contained in the line. The number of SubMessage definitions in each line of the the configuration file will
     * determine the number of SubMessage objects in the array.
     */
    while(reader.hasNextLine()) {
      String temp = reader.nextLine();

      if(!temp.substring(0, 2).equals("//")) {

        // Splits the configuration line by separating by commas.
        String[] data = temp.split(",");

        String msgId = data[0];
        int len = Integer.parseInt(data[1]);

        int skip = 2;
        if(msgId.equals("0203")) {
          skip = 4;
        }

        SubMessage[] subMessages = new SubMessage[len / skip];

        for(int i = 0; i < len / skip; i++) {
          if(data.length != (2 + 6 * (len / skip))) {
            display.error("Configuration Parsing Error",
                          "Invalid length. Missing sub-messages or there are missing values from a sub-message.");
            reader.close();
            return false;
          }

          if(data[2 + 6 * i].length() > 7) {
            display.error("Configuration Parsing Error",
                          "The length of a sub-message title must be less than or equal to 7 characters.");
            reader.close();
            return false;
          }

          if(data[7 + 6 * i].length() > 4) {
            display.error("Configuration Parsing Error",
                          "The length of a sub-message unit must be less than or equal to 4 characters.");
            reader.close();
            return false;
          }

          if(!(data[3 + 6 * i].equals("false") || data[3 + 6 * i].equals("true")) ||
              !(data[4 + 6 * i].equals("false") || data[4 + 6 * i].equals("true"))) {
            display.error("Configuration Parsing Error", "Booleans must have a value of 'true' or 'false'");
            reader.close();
            return false;
          }

          try {
            subMessages[i] = new SubMessage(data[2 + 6 * i], Boolean.parseBoolean(data[3 + 6 * i]),
                Boolean.parseBoolean(data[4 + 6 * i]), Float.parseFloat(data[5 + 6 * i]),
                Float.parseFloat(data[6 + 6 * i]), data[7 + 6 * i]);
          } catch(Exception e) {
            display.error("Configuration Parsing Error", "Parsing Error: " + e.getMessage());
            reader.close();
            return false;
          }
        }

        messageData.put(msgId, new Message(msgId, len, subMessages));
      }
    }

    reader.close();
    return true;
  }

  /**
   * This method is called when the Display signals that the user wants the translation to begin. Resets all instance
   * variables, then runs the various translation methods in the proper order.
   */
  public void begin() {
    this.convertProgress = 0;
    this.normalizeProgress = 0;
    this.printProgress = 0;

    long time = System.currentTimeMillis();

    this.axis = prepareAxis();

    if(!prepareFiles()) return;
    if(!convert()) return;
    generateDistance();
    normalize();
    outputValues();

    display.showTimeElapsed((System.currentTimeMillis() - time) / 1000.0);
  }

  /**
   * Prepares the first row of the array, which contains the labels and units for each column.
   *
   * @return A list of the column labels in the proper order.
   */
  private ArrayList<String> prepareAxis() {
    ArrayList<String> axis = new ArrayList<String>();

    axis.add("xtime [s] ");
    int lastColumnUsed = 0;

    axis.add("xdist [m] ");
    lastColumnUsed++;

    Iterator<Entry<String, Message>> iterator = messageData.entrySet().iterator();
    while(iterator.hasNext()) {
      Entry<String, Message> message = iterator.next();
      SubMessage[] subMessages = message.getValue().getSubMessages();

      for(int i = 0; i < subMessages.length; i++) {

        // Make sure we don't add Reserved or Unused columns to our data array.
        if(!(subMessages[i].getTitle().equals("Rsrvd") || subMessages[i].getTitle().equals("Unused"))) {
          lastColumnUsed++;
          message.getValue().getSubMessages()[i].setColumnIndex(lastColumnUsed);
          axis.add(subMessages[i].getTitle() + " [" + subMessages[i].getUnits() + "] ");
        }
      }
    }
    return axis;
  }

  /**
   * Ensures that the input and output files are valid and that the input file exists.
   *
   * @return A boolean representing the success of the checks.
   */
  private boolean prepareFiles() {
    if(inputFile == null || !inputFile.canRead() || !inputFile.exists() || inputFile.isDirectory() ||
        !inputFile.isFile()) {
      display.error("Invalid File Error", "Please choose a valid input file.");
      return false;
    }

    if(outputFile == null) {
      display.error("Invalid File Error", "Please specify an output file.");
      return false;
    }
    return true;
  }

  /**
   * Iterates through each message sent over the CAN bus and converts the raw data into the intended values. After the
   * completion of this method, the array will contain all values known from the data, but no interpolation between
   * messages of the same type will be done. So, many values will still be Float.MAX_VALUE.
   *
   * @return A boolean representing the success of the conversion.
   */
  private boolean convert() {
    byte[] data = null;
    try {
      data = Files.readAllBytes(inputFile.toPath());
    } catch(IOException e) {
      display.error("IO Error", "Please try again or try a different file.");
      return false;
    }

    this.values = new ArrayList<float[]>();

    /*
     * This iterator points to a byte in the data array. It will be incremented depending on the number of subMessages
     * in the current message.
     */
    int i = 0;
    while(true) {
      try {
        if(i >= data.length) break;

        int temp = (int) (Math.floor(((i / (float) data.length) * 100.0f)));
        if(temp > this.convertProgress) setProgress(0, temp);

        String msgId = hex(data[i + 1]) + hex(data[i]);
        if(messageData.containsKey(msgId)) {
          int len = messageData.get(msgId).getLength();

          byte[] timeBytes = new byte[] {data[i + len + 2], data[i + len + 3], data[i + len + 4], data[i + len + 5]};

          byte[] msgBytes = new byte[len];
          for(int j = 0; j < len; j++) {
            msgBytes[j] = data[j + i + 2];
          }

          values.add(messageData.get(msgId).translateData(msgBytes, timeBytes, this.axis.size()));
          i = i + len + 6;
        } else {
          /*
           * If we come to another message in the stream and can't identify it, then we will have to halt the conversion
           * because the length of the unknown message can't be determined. So, we don't know where the next valid
           * message starts.
           */
          display.error("Conversion Error", "Message Code Not Found - " + msgId);
          break;
        }
      } catch(ArrayIndexOutOfBoundsException e) {
        /*
         * If we've gone past the end of the array, we should end the loop gracefully without crashing. The only
         * possible negative side-effect of this is losing one CAN message at the end of the stream, which isn't a
         * problem.
         */
        break;
      }
    }

    setProgress(0, 100);
    return true;
  }

  /**
   * Iterates through all the messages which include ground speed and integrates the displacement over the time
   * differential to obtain a total distance traveled.
   */
  private void generateDistance() {

    // This gets the column index for the ground speed subMessage.
    int groundSpeedIndex = messageData.get("0203").getSubMessages()[0].getColumnIndex();

    for(int i = 0; i < values.size(); i++) {
      if(values.get(i)[groundSpeedIndex] != Float.MAX_VALUE) {
        int j = i - 1;
        while(j > 0 && values.get(j)[groundSpeedIndex] == Float.MAX_VALUE) {
          j--;
        }

        if(j > 0) {
          float average = (values.get(i)[groundSpeedIndex] + values.get(j)[groundSpeedIndex]) / 2.0f;
          float timediff = values.get(i)[0] - values.get(j)[0];

          // Converts from miles per hour seconds to meters.
          values.get(i)[2] = average * timediff * 0.44704f;;
        }
      }
    }

    float totalDistance = 0;
    for(int i = 0; i < values.size(); i++) {
      if(values.get(i)[2] != Float.MAX_VALUE) {
        totalDistance += values.get(i)[2];
        values.get(i)[2] = totalDistance;
      }
    }
  }

  /**
   * Takes the converted data and interpolates all values so that the data is seamless. This is necessary because some
   * sensors are sampled much more quickly than others, leaving gaps in the slowly-sampled data. Darab works by assuming
   * each sensor reports value at every time, so interpolation is required for the data to display properly without
   * gaps.
   */
  private void normalize() {

    /*
     * Searches for the first known sensor value and copies that value from the beginning of the data array to the point
     * where it was found. Essentially interpolates all the sensors which haven't been sampled yet to the first sampled
     * value.
     */
    for(int i = 1; i < values.get(0).length; i++) {
      for(int j = 0; j < values.size(); j++) {
        if(values.get(j)[i] != Float.MAX_VALUE) {
          for(int k = 0; k < j; k++) {
            values.get(k)[i] = values.get(j)[i];
          }
          break;
        }
      }
    }

    /*
     * Interpolates all values from one sample to the next sensor sample. Will replace all unknown values (represented
     * temporarily by Float.MAX_VALUE) with interpolated values.
     */
    for(int i = 1; i < values.get(0).length; i++) {
      int j = 0;
      float val = values.get(0)[i];
      while(j < values.size()) {
        if(values.get(j)[i] == Float.MAX_VALUE)
          values.get(j)[i] = val;
        else
          val = values.get(j)[i];
        j++;
      }
      int temp = (int) (Math.floor(((i / (float) values.get(0).length) * 100.0f)));
      if(temp > this.normalizeProgress) setProgress(1, temp);
    }

    /*
     * If we have any left over Float.MAX_VALUE values, set them to 0.0f instead. This may happen if a specific sensor
     * is never sampled.
     */
    for(int i = 0; i < values.get(0).length; i++) {
      for(int j = 0; j < values.size(); j++) {
        if(values.get(j)[i] == Float.MAX_VALUE) {
          values.get(j)[i] = 0.0f;
        }
      }
    }
    setProgress(1, 100);
  }

  /**
   * Writes the converted and normalized values to the output file.
   */
  private void outputValues() {
    try {
      FileWriter fw = new FileWriter(outputFile.getAbsoluteFile());
      BufferedWriter bw = new BufferedWriter(fw, 32768);

      for(String e: axis) {
        bw.write(e + " ");
      }
      bw.write("\n");

      for(int i = 0; i < values.size(); i++) {
        float[] row = values.get(i);

        bw.write(row[0] + " ");
        for(int j = 1; j < row.length; j++) {
          bw.write(row[j] + " ");
        }

        int temp = (int) (Math.floor(((i / (float) values.size()) * 100.0f)));
        if(temp > this.printProgress) setProgress(2, temp);

        bw.write("\n");
      }

      bw.flush();
      bw.close();

      setProgress(2, 100);
    } catch(IOException e) {
      display.error("IO Error", "Please try again or try a different file.");
      return;
    }
  }

  /**
   * Sets the progress of one specific section of the translation process.
   *
   * @param which Which section to set the progress of.
   * @param progress The progress to update to.
   */
  private void setProgress(int which, int progress) {
    display.setProgress(which, progress);
    switch(which) {
      case 0:
        this.convertProgress = progress;
        return;
      case 1:
        this.normalizeProgress = progress;
        return;
      case 2:
        this.printProgress = progress;
        return;
    }
  }

  /**
   * Takes the byte input and returns a String representation in hex.
   *
   * @param num A number in the form of a byte object.
   * @return A string representation of num in hex format.
   */
  private String hex(byte num) {
    return String.format("%02x", num);
  }

  public void setInputFile(File file) {
    this.inputFile = file;
  }

  public void setOutputFile(File file) {
    this.outputFile = file;
  }
}


text:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
?><!DOCTYPE html>

<meta charset="utf-8">
<title>BlackWallet - 404 Page Not Found</title>

<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">

<link type="text/css" rel="stylesheet" href="https://blackwallet.co/assets/css/materialize.min.css"  media="screen,projection"/>
<link rel="icon" type="image/png" href="https://blackwallet.co/assets/img/icon.png" />
<link type="text/css" rel="stylesheet" href="https://blackwallet.co/assets/css/stars.css"  media="screen,projection"/>'

<link rel="apple-touch-icon" sizes="120x120" href="https://blackwallet.co/assets/img/apple-touch-icon-120x120-precomposed.png" />
<link rel="apple-touch-icon" sizes="152x152" href="https://blackwallet.co/assets/img/apple-touch-icon-152x152-precomposed.png" />

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

 blackwallet

<?php echo $heading; ?>

<?php echo $message; ?>

This page has been moved, has been removed, or is not available for public acccess.

	If you think it's an error, please report it on our official thread.


text:
<!DOCTYPE HTML>

		<meta charset="utf-8">
		<title>Collapsible Content Blocks</title>
		<link rel="stylesheet" href="lib/BlackBerry-JQM-all.css" />

Collapsible Content Blocks

 Collapsed

 This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus et hendrerit sapien. Quisque non augue eget ante blandit dapibus ut quis lacus. Praesent lobortis suscipit massa, eu rhoncus sapien rutrum at. Vivamus enim enim, cursus egestas lobortis vel, semper et turpis.

 Expanded

 This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus et hendrerit sapien. Quisque non augue eget ante blandit dapibus ut quis lacus. Praesent lobortis suscipit massa, eu rhoncus sapien rutrum at. Vivamus enim enim, cursus egestas lobortis vel, semper et turpis.

 Expanded With Content Theme

 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus et hendrerit sapien. Quisque non augue eget ante blandit dapibus ut quis lacus. Praesent lobortis suscipit massa, eu rhoncus sapien rutrum at. Vivamus enim enim, cursus egestas lobortis vel, semper et turpis. Nunc eleifend nisi non augue porta tincidunt. Proin viverra aliquet ultrices. In hac habitasse platea dictumst. Curabitur elementum molestie purus quis pellentesque. Cras et velit dui. Donec eget ornare turpis. Donec nibh quam, blandit vitae sollicitudin a, elementum a velit. Curabitur pharetra tortor sagittis justo cursus ornare. Nulla purus mauris, egestas sit amet egestas ac, suscipit in tellus. Duis orci quam, tempor a placerat ut, faucibus sed massa. Ut commodo accumsan eros, vitae imperdiet velit sodales rutrum.