mirror of https://github.com/odrling/Aegisub
Some far-from-complete ASS file writing.
Originally committed to SVN as r2035.
This commit is contained in:
parent
a3755cc6e4
commit
18e6684cff
|
@ -324,6 +324,14 @@
|
|||
RelativePath=".\src\text_file_reader.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\text_file_writer.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\text_file_writer.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\time.cpp"
|
||||
>
|
||||
|
|
|
@ -46,6 +46,7 @@ namespace Aegilib {
|
|||
|
||||
public:
|
||||
virtual void Load(wxInputStream &file,const String encoding) = 0;
|
||||
virtual void Save(wxOutputStream &file,const String encoding) = 0;
|
||||
};
|
||||
typedef shared_ptr<FormatHandler> FormatHandlerPtr;
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#pragma once
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <wx/wfstream.h>
|
||||
#include "manipulator.h"
|
||||
#include "section.h"
|
||||
|
@ -56,7 +57,7 @@ namespace Aegilib {
|
|||
typedef shared_ptr<Format> FormatPtr;
|
||||
|
||||
private:
|
||||
std::list<SectionPtr> sections;
|
||||
std::vector<SectionPtr> sections;
|
||||
ActionStack undoStack;
|
||||
ActionStack redoStack;
|
||||
ViewList listeners;
|
||||
|
@ -75,8 +76,10 @@ namespace Aegilib {
|
|||
void LoadFile(const String filename,const String encoding=L"");
|
||||
void SaveFile(const String filename,const String encoding=L"UTF-8");
|
||||
|
||||
SectionPtr GetSection(String name) const;
|
||||
void AddSection(String name);
|
||||
SectionPtr GetSection(String name) const;
|
||||
SectionPtr GetSectionByIndex(size_t index) const;
|
||||
size_t GetSectionCount() const;
|
||||
|
||||
bool CanUndo(const String owner=L"") const;
|
||||
bool CanRedo(const String owner=L"") const;
|
||||
|
|
|
@ -44,9 +44,8 @@ namespace Aegilib {
|
|||
|
||||
// Section class
|
||||
class Section {
|
||||
friend class shared_ptr<Section>;
|
||||
private:
|
||||
std::list<SectionEntryPtr> entries;
|
||||
std::vector<SectionEntryPtr> entries;
|
||||
std::map<String,String> properties;
|
||||
String name;
|
||||
|
||||
|
@ -54,17 +53,24 @@ namespace Aegilib {
|
|||
Section(String name);
|
||||
~Section() {}
|
||||
|
||||
const String& GetName() const { return name; }
|
||||
// Section name
|
||||
String SetName(const String& newName) { name = newName; }
|
||||
const String& GetName() const { return name; }
|
||||
|
||||
// Script properties
|
||||
void SetProperty(const String &key,const String &value);
|
||||
void UnsetProperty(const String &key);
|
||||
String GetProperty(const String &key) const;
|
||||
bool HasProperty(const String &key) const;
|
||||
size_t PropertyCount() const;
|
||||
size_t GetPropertyCount() const;
|
||||
String GetPropertyName(size_t index) const;
|
||||
|
||||
// Entries
|
||||
void AddEntry(SectionEntryPtr entry);
|
||||
void RemoveEntryByIndex(size_t index);
|
||||
void RemoveEntry(SectionEntryPtr entry);
|
||||
SectionEntryConstPtr GetEntry(size_t index) const;
|
||||
size_t GetEntryCount() const;
|
||||
};
|
||||
typedef shared_ptr<Section> SectionPtr;
|
||||
|
||||
|
|
|
@ -49,18 +49,24 @@ namespace Aegilib {
|
|||
};
|
||||
|
||||
// Prototypes
|
||||
class SectionEntryPlain;
|
||||
typedef shared_ptr<SectionEntryPlain> SectionEntryPlainPtr;
|
||||
class SectionEntryDialogue;
|
||||
typedef shared_ptr<SectionEntryDialogue> SectionEntryDialoguePtr;
|
||||
class SectionEntryStyle;
|
||||
typedef shared_ptr<SectionEntryStyle> SectionEntryStylePtr;
|
||||
class SectionEntryFile;
|
||||
typedef shared_ptr<SectionEntryFile> SectionEntryFilePtr;
|
||||
class SectionEntryRaw;
|
||||
typedef shared_ptr<SectionEntryRaw> SectionEntryRawPtr;
|
||||
class SectionEntry;
|
||||
class SectionEntryPlain;
|
||||
class SectionEntryDialogue;
|
||||
class SectionEntryStyle;
|
||||
class SectionEntryFile;
|
||||
class SectionEntryRaw;
|
||||
typedef shared_ptr<SectionEntry> SectionEntryPtr;
|
||||
typedef shared_ptr<SectionEntryPlain> SectionEntryPlainPtr;
|
||||
typedef shared_ptr<SectionEntryDialogue> SectionEntryDialoguePtr;
|
||||
typedef shared_ptr<SectionEntryStyle> SectionEntryStylePtr;
|
||||
typedef shared_ptr<SectionEntryFile> SectionEntryFilePtr;
|
||||
typedef shared_ptr<SectionEntryRaw> SectionEntryRawPtr;
|
||||
typedef shared_ptr<const SectionEntry> SectionEntryConstPtr;
|
||||
typedef shared_ptr<const SectionEntryPlain> SectionEntryPlainConstPtr;
|
||||
typedef shared_ptr<const SectionEntryDialogue> SectionEntryDialogueConstPtr;
|
||||
typedef shared_ptr<const SectionEntryStyle> SectionEntryStyleConstPtr;
|
||||
typedef shared_ptr<const SectionEntryFile> SectionEntryFileConstPtr;
|
||||
typedef shared_ptr<const SectionEntryRaw> SectionEntryRawConstPtr;
|
||||
|
||||
// Section entry class
|
||||
class SectionEntry {
|
||||
|
@ -72,6 +78,7 @@ namespace Aegilib {
|
|||
virtual SectionEntryType GetType() const =0;
|
||||
static const SectionEntryPlainPtr GetAsPlain(const SectionEntryPtr &ptr);
|
||||
static const SectionEntryDialoguePtr GetAsDialogue(const SectionEntryPtr &ptr);
|
||||
static const SectionEntryDialogueConstPtr GetAsDialogue(const SectionEntryConstPtr &ptr);
|
||||
static const SectionEntryStylePtr GetAsStyle(const SectionEntryPtr &ptr);
|
||||
static const SectionEntryFilePtr GetAsFile(const SectionEntryPtr &ptr);
|
||||
static const SectionEntryRawPtr GetAsRaw(const SectionEntryPtr &ptr);
|
||||
|
|
|
@ -33,9 +33,11 @@
|
|||
// Contact: mailto:amz@aegisub.net
|
||||
//
|
||||
|
||||
#include "section.h"
|
||||
#include "model.h"
|
||||
#include "format_ass.h"
|
||||
#include "../text_file_reader.h"
|
||||
#include "../text_file_writer.h"
|
||||
#include <iostream>
|
||||
#include <wx/tokenzr.h>
|
||||
using namespace Aegilib;
|
||||
|
@ -78,10 +80,6 @@ void FormatHandlerASS::Load(wxInputStream &file,const String encoding)
|
|||
// Make text file reader
|
||||
TextFileReader reader(file,encoding);
|
||||
|
||||
// Debug
|
||||
using namespace std;
|
||||
cout << endl << "Dumping file:" << endl;
|
||||
|
||||
// Variables
|
||||
int version = 1;
|
||||
wxString curGroup = L"-";
|
||||
|
@ -111,17 +109,44 @@ void FormatHandlerASS::Load(wxInputStream &file,const String encoding)
|
|||
SectionEntryPtr entry = MakeEntry(cur,section,version);
|
||||
if (entry) section->AddEntry(entry);
|
||||
}
|
||||
|
||||
// Debug
|
||||
cout << "\nFinished reading file with version=" << version << ".\n";
|
||||
cout << "Dumping properties:\n";
|
||||
section = model.GetSection(_T("Script Info"));
|
||||
size_t n = section->PropertyCount();
|
||||
for (size_t i=0;i<n;i++) {
|
||||
wxString name = section->GetPropertyName(i);
|
||||
cout << name.mb_str(wxConvUTF8) << "=" << section->GetProperty(name).mb_str(wxConvUTF8) << endl;
|
||||
}
|
||||
cout << "Done.\n" << endl;
|
||||
|
||||
|
||||
/////////////////////
|
||||
// Save file to disc
|
||||
void FormatHandlerASS::Save(wxOutputStream &file,const String encoding)
|
||||
{
|
||||
// Make text file writer
|
||||
TextFileWriter writer(file,encoding);
|
||||
|
||||
// Set up list of sections to write
|
||||
wxArrayString sections;
|
||||
sections.Add(L"Script Info");
|
||||
sections.Add(L"V4+ Styles");
|
||||
sections.Add(L"Events");
|
||||
sections.Add(L"Fonts");
|
||||
sections.Add(L"Graphics");
|
||||
|
||||
// Look for remaining sections
|
||||
size_t totalSections = model.GetSectionCount();
|
||||
for (size_t i=0;i<totalSections;i++) {
|
||||
String name = model.GetSectionByIndex(i)->GetName();
|
||||
if (sections.Index(name,false,false) == wxNOT_FOUND) sections.Add(name);
|
||||
}
|
||||
|
||||
// Write sections
|
||||
size_t len = sections.size();
|
||||
for (size_t i=0;i<len;i++) {
|
||||
// See if it exists
|
||||
SectionPtr section = model.GetSection(sections[i]);
|
||||
if (section) {
|
||||
// Add a spacer
|
||||
if (i != 0) writer.WriteLineToFile(_T(""));
|
||||
|
||||
// Write the section
|
||||
WriteSection(writer,section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -264,3 +289,27 @@ void FormatHandlerASS::ProcessGroup(String cur,String &curGroup,int &version) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////
|
||||
// Write a section to the file
|
||||
void FormatHandlerASS::WriteSection(TextFileWriter &writer,SectionPtr section)
|
||||
{
|
||||
// Write name
|
||||
writer.WriteLineToFile(_T("[") + section->GetName() + _T("]"));
|
||||
|
||||
// Write properties
|
||||
size_t props = section->GetPropertyCount();
|
||||
for (size_t i=0;i<props;i++) {
|
||||
String name = section->GetPropertyName(i);
|
||||
writer.WriteLineToFile(name + _T(": ") + section->GetProperty(name));
|
||||
}
|
||||
|
||||
// Write contents
|
||||
size_t entries = section->GetEntryCount();
|
||||
for (size_t i=0;i<entries;i++) {
|
||||
SectionEntryConstPtr entry = section->GetEntry(i);
|
||||
shared_ptr<const SerializeText> serial = dynamic_pointer_cast<const SerializeText>(entry);
|
||||
writer.WriteLineToFile(serial->ToText());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,19 +44,29 @@ namespace Aegilib {
|
|||
|
||||
// Prototypes
|
||||
class Model;
|
||||
class TextFileWriter;
|
||||
|
||||
class SerializeText {
|
||||
public:
|
||||
virtual ~SerializeText(){}
|
||||
virtual String ToText() const=0;
|
||||
};
|
||||
|
||||
// Advanced Substation Alpha format handler
|
||||
class FormatHandlerASS : public FormatHandler {
|
||||
private:
|
||||
Model &model;
|
||||
|
||||
SectionEntryPtr MakeEntry(const String &data,SectionPtr section,int version);
|
||||
void ProcessGroup(String cur,String &curGroup,int &version);
|
||||
Model &model;
|
||||
void WriteSection(TextFileWriter &writer,SectionPtr section);
|
||||
|
||||
public:
|
||||
FormatHandlerASS(Model &model);
|
||||
~FormatHandlerASS();
|
||||
|
||||
void Load(wxInputStream &file,const String encoding);
|
||||
void Save(wxOutputStream &file,const String encoding);
|
||||
};
|
||||
|
||||
// Advanced Substation Alpha format
|
||||
|
@ -76,7 +86,7 @@ namespace Aegilib {
|
|||
};
|
||||
|
||||
// Dialogue
|
||||
class DialogueASS : public SectionEntryDialogue {
|
||||
class DialogueASS : public SectionEntryDialogue, public SerializeText {
|
||||
private:
|
||||
String text;
|
||||
String style;
|
||||
|
@ -88,6 +98,7 @@ namespace Aegilib {
|
|||
bool isComment;
|
||||
|
||||
bool Parse(String data,int version);
|
||||
String ToText() const;
|
||||
|
||||
public:
|
||||
// Constructors
|
||||
|
@ -123,7 +134,7 @@ namespace Aegilib {
|
|||
};
|
||||
|
||||
// Style
|
||||
class StyleASS : public SectionEntryStyle {
|
||||
class StyleASS : public SectionEntryStyle, public SerializeText {
|
||||
private:
|
||||
String name;
|
||||
String font;
|
||||
|
@ -152,6 +163,7 @@ namespace Aegilib {
|
|||
bool Parse(String data,int version);
|
||||
int AlignSSAtoASS(int ssaAlignment);
|
||||
int AlignASStoSSA(int assAlignment);
|
||||
String ToText() const;
|
||||
|
||||
public:
|
||||
// Constructors
|
||||
|
|
|
@ -134,3 +134,12 @@ bool DialogueASS::Parse(wxString rawData, int version)
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/////////////
|
||||
// Serialize
|
||||
String DialogueASS::ToText() const
|
||||
{
|
||||
String final = L"Dialogue";
|
||||
return final;
|
||||
}
|
||||
|
|
|
@ -163,3 +163,12 @@ int StyleASS::AlignASStoSSA(int assAlignment)
|
|||
// TODO
|
||||
return assAlignment;
|
||||
}
|
||||
|
||||
|
||||
/////////////
|
||||
// Serialize
|
||||
String StyleASS::ToText() const
|
||||
{
|
||||
String final = L"Style";
|
||||
return final;
|
||||
}
|
||||
|
|
|
@ -109,10 +109,20 @@ void Model::Load(wxInputStream &input,const FormatPtr format,const String encodi
|
|||
// Save subtitles
|
||||
void Model::Save(wxOutputStream &output,const FormatPtr format,const String encoding)
|
||||
{
|
||||
(void) output;
|
||||
(void) format;
|
||||
(void) encoding;
|
||||
// Autodetect format
|
||||
if (format == NULL) {
|
||||
// TODO
|
||||
|
||||
// No format found
|
||||
throw Exception(Exception::No_Format_Handler);
|
||||
}
|
||||
|
||||
// Get handler
|
||||
FormatHandlerPtr handler = format->GetHandler(*this);
|
||||
if (!handler) throw Exception(Exception::No_Format_Handler);
|
||||
|
||||
// Load
|
||||
handler->Save(output,encoding);
|
||||
}
|
||||
|
||||
|
||||
|
@ -136,18 +146,6 @@ void Model::SaveFile(const String filename,const String encoding)
|
|||
}
|
||||
|
||||
|
||||
//////////////////
|
||||
// Gets a section
|
||||
SectionPtr Model::GetSection(String name) const
|
||||
{
|
||||
std::list<SectionPtr>::const_iterator cur;
|
||||
for (cur=sections.begin();cur!=sections.end();cur++) {
|
||||
if ((*cur)->GetName() == name) return *cur;
|
||||
}
|
||||
return SectionPtr();
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////
|
||||
// Inserts a new section
|
||||
void Model::AddSection(String name)
|
||||
|
@ -156,3 +154,31 @@ void Model::AddSection(String name)
|
|||
if (prev) throw Exception(Exception::Section_Already_Exists);
|
||||
sections.push_back(SectionPtr(new Section(name)));
|
||||
}
|
||||
|
||||
|
||||
//////////////////
|
||||
// Gets a section
|
||||
SectionPtr Model::GetSection(String name) const
|
||||
{
|
||||
size_t len = sections.size();
|
||||
for (size_t i=0;i<len;i++) {
|
||||
if (sections[i]->GetName() == name) return sections[i];
|
||||
}
|
||||
return SectionPtr();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////
|
||||
// Get section by index
|
||||
SectionPtr Model::GetSectionByIndex(size_t index) const
|
||||
{
|
||||
return sections.at(index);
|
||||
}
|
||||
|
||||
|
||||
/////////////////////
|
||||
// Get section count
|
||||
size_t Model::GetSectionCount() const
|
||||
{
|
||||
return sections.size();
|
||||
}
|
||||
|
|
|
@ -53,6 +53,32 @@ void Section::AddEntry(SectionEntryPtr entry)
|
|||
entries.push_back(entry);
|
||||
}
|
||||
|
||||
void Section::RemoveEntryByIndex(size_t index)
|
||||
{
|
||||
entries.erase(entries.begin()+index);
|
||||
}
|
||||
|
||||
void Section::RemoveEntry(SectionEntryPtr entry)
|
||||
{
|
||||
size_t len = entries.size();
|
||||
for (size_t i=0;i<len;i++) {
|
||||
if (entries[i] == entry) {
|
||||
entries.erase(entries.begin()+i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SectionEntryConstPtr Section::GetEntry(size_t index) const
|
||||
{
|
||||
return entries[index];
|
||||
}
|
||||
|
||||
size_t Section::GetEntryCount() const
|
||||
{
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
|
||||
//////////////////
|
||||
// Set a property
|
||||
|
@ -91,7 +117,7 @@ bool Section::HasProperty(const String &key) const
|
|||
|
||||
//////////////////////
|
||||
// Get property count
|
||||
size_t Section::PropertyCount() const
|
||||
size_t Section::GetPropertyCount() const
|
||||
{
|
||||
return properties.size();
|
||||
}
|
||||
|
|
|
@ -57,6 +57,10 @@ const SectionEntryDialoguePtr SectionEntry::GetAsDialogue(const SectionEntryPtr
|
|||
{
|
||||
return dynamic_pointer_cast<SectionEntryDialogue>(ptr);
|
||||
}
|
||||
const SectionEntryDialogueConstPtr SectionEntry::GetAsDialogue(const SectionEntryConstPtr &ptr)
|
||||
{
|
||||
return dynamic_pointer_cast<const SectionEntryDialogue>(ptr);
|
||||
}
|
||||
const SectionEntryStylePtr SectionEntry::GetAsStyle(const SectionEntryPtr &ptr)
|
||||
{
|
||||
return dynamic_pointer_cast<SectionEntryStyle>(ptr);
|
||||
|
|
|
@ -38,76 +38,38 @@
|
|||
// Headers
|
||||
#include <fstream>
|
||||
#include "text_file_writer.h"
|
||||
using namespace Aegilib;
|
||||
|
||||
|
||||
///////////////
|
||||
// Constructor
|
||||
TextFileWriter::TextFileWriter(Aegilib::String _filename,Aegilib::String enc) {
|
||||
TextFileWriter::TextFileWriter(wxOutputStream &stream,String enc)
|
||||
: file(stream)
|
||||
{
|
||||
// Setup
|
||||
open = false;
|
||||
customConv = false;
|
||||
IsFirst = true;
|
||||
filename = _filename;
|
||||
|
||||
// Set encoding
|
||||
encoding = enc;
|
||||
if (encoding == _T("Local")) conv = &wxConvLocal;
|
||||
if (encoding == _T("Local")) conv = shared_ptr<wxMBConv> (wxConvCurrent,NullDeleter());
|
||||
else {
|
||||
if (encoding.IsEmpty()) encoding = _T("UTF-8");
|
||||
if (encoding == _T("US-ASCII")) encoding = _T("ISO-8859-1");
|
||||
conv = new wxCSConv(encoding);
|
||||
customConv = true;
|
||||
conv = shared_ptr<wxMBConv> (new wxCSConv(encoding));
|
||||
IsUnicode = encoding.Left(3) == _T("UTF");
|
||||
}
|
||||
|
||||
// Open file
|
||||
Open();
|
||||
}
|
||||
|
||||
|
||||
//////////////
|
||||
// Destructor
|
||||
TextFileWriter::~TextFileWriter() {
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
/////////////
|
||||
// Open file
|
||||
void TextFileWriter::Open() {
|
||||
// Open file
|
||||
if (open) return;
|
||||
#ifdef WIN32
|
||||
file.open(filename.wc_str(),std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
#else
|
||||
file.open(wxFNCONV(filename),std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
#endif
|
||||
if (!file.is_open()) {
|
||||
throw _T("Failed opening file for writing.");
|
||||
}
|
||||
open = true;
|
||||
|
||||
// Set encoding
|
||||
SetEncoding();
|
||||
}
|
||||
|
||||
|
||||
//////////////
|
||||
// Close file
|
||||
void TextFileWriter::Close() {
|
||||
if (!open) return;
|
||||
file.close();
|
||||
open = false;
|
||||
if (customConv) delete conv;
|
||||
}
|
||||
|
||||
|
||||
/////////////////
|
||||
// Write to file
|
||||
void TextFileWriter::WriteLineToFile(Aegilib::String line,bool addLineBreak) {
|
||||
// Make sure it's loaded
|
||||
if (!open) Open();
|
||||
|
||||
// Add line break
|
||||
wxString temp = line;
|
||||
if (addLineBreak) temp += _T("\r\n");
|
||||
|
@ -125,7 +87,7 @@ void TextFileWriter::WriteLineToFile(Aegilib::String line,bool addLineBreak) {
|
|||
if (!buf.data())
|
||||
return;
|
||||
size_t len = wcslen(buf.data());
|
||||
file.write((const char*)buf.data(),(std::streamsize)len*sizeof(wchar_t));
|
||||
file.Write((const char*)buf.data(),(std::streamsize)len*sizeof(wchar_t));
|
||||
}
|
||||
|
||||
// 8-bit
|
||||
|
@ -134,7 +96,7 @@ void TextFileWriter::WriteLineToFile(Aegilib::String line,bool addLineBreak) {
|
|||
if (!buf.data())
|
||||
return;
|
||||
size_t len = strlen(buf.data());
|
||||
file.write(buf.data(),(std::streamsize)len);
|
||||
file.Write(buf.data(),(std::streamsize)len);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,42 +34,28 @@
|
|||
//
|
||||
|
||||
|
||||
#ifndef TEXT_FILE_WRITER_H
|
||||
#define TEXT_FILE_WRITER_H
|
||||
#pragma once
|
||||
#include "aegilib.h"
|
||||
#include <wx/stream.h>
|
||||
|
||||
|
||||
///////////
|
||||
// Headers
|
||||
#include <wx/wxprec.h>
|
||||
#include <wx/string.h>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
/////////
|
||||
// Class
|
||||
namespace Aegilib {
|
||||
class TextFileWriter {
|
||||
private:
|
||||
wxString filename;
|
||||
wxString encoding;
|
||||
std::ofstream file;
|
||||
wxOutputStream &file;
|
||||
|
||||
wxMBConv *conv;
|
||||
bool customConv;
|
||||
bool open;
|
||||
shared_ptr<wxMBConv> conv;
|
||||
bool Is16;
|
||||
bool IsFirst;
|
||||
bool IsUnicode;
|
||||
|
||||
void Open();
|
||||
void Close();
|
||||
void SetEncoding();
|
||||
|
||||
public:
|
||||
TextFileWriter(Aegilib::String filename,Aegilib::String encoding=_T(""));
|
||||
TextFileWriter(wxOutputStream &stream,String encoding=_T(""));
|
||||
~TextFileWriter();
|
||||
|
||||
void WriteLineToFile(Aegilib::String line,bool addLineBreak=true);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue