diff --git a/src/OneScript.StandardLibrary/Xml/XmlWriterImpl.cs b/src/OneScript.StandardLibrary/Xml/XmlWriterImpl.cs index abb1b3d48..4cdaea8d8 100644 --- a/src/OneScript.StandardLibrary/Xml/XmlWriterImpl.cs +++ b/src/OneScript.StandardLibrary/Xml/XmlWriterImpl.cs @@ -1,346 +1,380 @@ -/*---------------------------------------------------------- -This Source Code Form is subject to the terms of the -Mozilla Public License, v.2.0. If a copy of the MPL -was not distributed with this file, You can obtain one -at http://mozilla.org/MPL/2.0/. -----------------------------------------------------------*/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using OneScript.Contexts; -using OneScript.Exceptions; -using OneScript.Types; -using OneScript.Values; -using ScriptEngine.Machine; -using ScriptEngine.Machine.Contexts; - -namespace OneScript.StandardLibrary.Xml -{ - [ContextClass("ЗаписьXML", "XMLWriter")] - public class XmlWriterImpl : AutoContext, IDisposable - { - private TextWriterWithSettings _internalTextWriter; - private XmlTextWriter _writer; - private XmlWriterSettingsImpl _settings = XmlWriterSettingsImpl.Constructor(); - private int _depth; - private readonly Stack> _nsmap = new(); - private StringBuilder _stringBuffer; - - private const string DEFAULT_INDENT_STRING = " "; - - public XmlWriterImpl() - { - Indent = true; - _nsmap.Push(new Dictionary()); - } - - private void EnterScope() - { - ++_depth; - var newMap = _nsmap.Peek().ToDictionary((kv) => kv.Key, (kv) => kv.Value); - _nsmap.Push(newMap); - } - - private void ExitScope() - { - _nsmap.Pop(); - --_depth; - } - - private void CheckIfOpen() - { - if (_writer == null) - throw NotOpenException(); - } - - #region Properties - - [ContextProperty("Отступ","Indent")] - public bool Indent { get; set; } - - [ContextProperty("КонтекстПространствИмен", "NamespaceContext")] - public XmlNamespaceContext NamespaceContext - { - get - { - return new XmlNamespaceContext(_depth, _nsmap.Peek()); - } - } - - [ContextProperty("Параметры", "Settings")] - public XmlWriterSettingsImpl Settings - { - get { return _settings; } - } - - #endregion - - #region Methods - - [ContextMethod("ЗаписатьАтрибут","WriteAttribute")] - public void WriteAttribute(string localName, string valueOrNamespace, string value = null) - { - CheckIfOpen(); - if (value == null) - { - _writer.WriteAttributeString(localName, valueOrNamespace); - } - else - { - _writer.WriteAttributeString(localName, valueOrNamespace, value); - } - } - - [ContextMethod("ЗаписатьБезОбработки","WriteRaw")] - public void WriteRaw(string data) - { - CheckIfOpen(); - _writer.WriteRaw(data); - } - - [ContextMethod("ЗаписатьИнструкциюОбработки","WriteProcessingInstruction")] - public void WriteProcessingInstruction(string name, string text) - { - CheckIfOpen(); - _writer.WriteProcessingInstruction(name, text); - } - - [ContextMethod("ЗаписатьКомментарий","WriteComment")] - public void WriteComment(string text) - { - CheckIfOpen(); - _writer.WriteComment(text); - } - - [ContextMethod("ЗаписатьКонецАтрибута","WriteEndAttribute")] - public void WriteEndAttribute() - { - CheckIfOpen(); - _writer.WriteEndAttribute(); - } - - [ContextMethod("ЗаписатьКонецЭлемента","WriteEndElement")] - public void WriteEndElement() - { - CheckIfOpen(); - _internalTextWriter.TrimEndSlashes = true; - _writer.WriteEndElement(); - _internalTextWriter.TrimEndSlashes = false; - ExitScope(); - } - - [ContextMethod("ЗаписатьНачалоАтрибута","WriteStartAttribute")] - public void WriteStartAttribute(string name, string ns = null) - { - CheckIfOpen(); - if (ns == null) - { - _writer.WriteStartAttribute(name); - } - else - { - _writer.WriteStartAttribute(name, ns); - } - - } - - [ContextMethod("ЗаписатьНачалоЭлемента","WriteStartElement")] - public void WriteStartElement(string name, string ns = null) - { - CheckIfOpen(); - if (ns == null) - { - _writer.WriteStartElement(name); - } - else - { - _writer.WriteStartElement(name, ns); - } - EnterScope(); - } - - [ContextMethod("ЗаписатьОбъявлениеXML","WriteXMLDeclaration")] - public void WriteXMLDeclaration() - { - CheckIfOpen(); - _writer.WriteStartDocument(); - } - - [ContextMethod("ЗаписатьСекциюCDATA","WriteCDATASection")] - public void WriteCDATASection(string data) - { - CheckIfOpen(); - _writer.WriteCData(data); - } - - [ContextMethod("ЗаписатьСоответствиеПространстваИмен","WriteNamespaceMapping")] - public void WriteNamespaceMapping(string prefix, string uri) - { - CheckIfOpen(); - _writer.WriteAttributeString("xmlns", prefix, null, uri); - _nsmap.Peek()[prefix] = uri; - } - - [ContextMethod("ЗаписатьСсылкуНаСущность","WriteEntityReference")] - public void WriteEntityReference(string name) - { - CheckIfOpen(); - _writer.WriteEntityRef(name); - } - - [ContextMethod("ЗаписатьТекст","WriteText")] - public void WriteText(string text) - { - CheckIfOpen(); - _writer.WriteString(text); - } - - [ContextMethod("ЗаписатьТекущий","WriteCurrent")] - public void WriteCurrent(XmlReaderImpl reader) - { - CheckIfOpen(); - var nodeType = reader.NodeType.UnderlyingValue; - switch (nodeType) - { - case XmlNodeType.Element: - CopyElementAndAttributes(reader, this); - break; - case XmlNodeType.XmlDeclaration: - WriteXMLDeclaration(); - break; - case XmlNodeType.DocumentType: - CopyDocumentType(reader, this); - break; - case XmlNodeType.Attribute: - CopyAttribute(reader.Name, reader.Value, reader, this); - break; - case XmlNodeType.EndElement: - WriteEndElement(); - break; - case XmlNodeType.CDATA: - WriteCDATASection(reader.Value); - break; - case XmlNodeType.Text: - WriteText(reader.Value); - break; - case XmlNodeType.Whitespace: - case XmlNodeType.SignificantWhitespace: - WriteText(reader.Value); - break; - case XmlNodeType.Comment: - WriteComment(reader.Value); - break; - case XmlNodeType.EntityReference: - WriteEntityReference(reader.Name); - break; - case XmlNodeType.ProcessingInstruction: - WriteProcessingInstruction(reader.Name, reader.Value); - break; - case XmlNodeType.Entity: - case XmlNodeType.EndEntity: - case XmlNodeType.Document: - case XmlNodeType.DocumentFragment: - case XmlNodeType.Notation: - throw CopyingNotSupportedException(nodeType); - - default: - break; - } - } - - private static void CopyDocumentType(XmlReaderImpl reader, XmlWriterImpl writer) - { - writer.WriteDocumentType(reader.Name, reader.Value); - } - - private static void CopyElementAndAttributes(XmlReaderImpl reader, XmlWriterImpl writer) - { - writer.WriteStartElement(reader.Name); - var attributeCount = reader.AttributeCount(); - if (attributeCount != 0) - { - for (var attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) - { - var attributeName = reader.AttributeName(attributeIndex); - var attributeValue = reader.GetAttribute(ValueFactory.Create(attributeIndex)).ExplicitString(); - CopyAttribute(attributeName, attributeValue, reader, writer); - } - } - } - - private static void CopyAttribute(string attributeNameIn, string attributeValue, XmlReaderImpl reader, XmlWriterImpl writer) - { - var splittedName = splitName(attributeNameIn); - if (!string.IsNullOrEmpty(splittedName.prefix)) - { - var readerNsContext = (XmlNamespaceContext)reader.NamespaceContext; - var writerNsContext = (XmlNamespaceContext)writer.NamespaceContext; - if (splittedName.prefix.Equals("xmlns", StringComparison.Ordinal)) - { - writer.WriteNamespaceMapping(splittedName.localName, attributeValue); - return; - } - else - { - var uri = readerNsContext.LookupNamespaceUri(splittedName.prefix); - if (uri is BslStringValue) - { - var value = uri.ExplicitString(); - var currentPrefix = writerNsContext.LookupPrefix(value); - if (!(currentPrefix is BslStringValue && currentPrefix.ExplicitString().Equals(splittedName.prefix, StringComparison.Ordinal))) - { - writer.WriteNamespaceMapping(splittedName.prefix, value); - } - splittedName.prefix = value; - } - } - } - writer.WriteAttribute(splittedName.localName, splittedName.prefix, attributeValue); - } - - private static (string prefix, string localName) splitName(string nameOrLocalName) - { - var parts = nameOrLocalName.Split(':'); - if (parts.Length > 1) return (parts[0], parts[1]); - if (parts[0].Equals("xmlns", StringComparison.Ordinal)) return (parts[0], ""); - return ("", parts[0]); - } - - [ContextMethod("ЗаписатьТипДокумента","WriteDocumentType")] - public void WriteDocumentType(string name, string varArg2, string varArg3 = null, string varArg4 = null) - { - CheckIfOpen(); - if (varArg4 != null) - { - _writer.WriteDocType(name, varArg2, varArg3, varArg4); - } - else if(varArg3 != null) - { - _writer.WriteDocType(name, null, varArg2, varArg3); - } - else - { - _writer.WriteDocType(name, null, null, varArg2); - } - } - - [ContextMethod("НайтиПрефикс","LookupPrefix")] - public IValue LookupPrefix(string uri) - { - CheckIfOpen(); - string prefix = _writer.LookupPrefix(uri); - if (prefix == null) - return ValueFactory.Create(); - return ValueFactory.Create(prefix); - } - - [ContextMethod("Закрыть","Close")] - public IValue Close() - { +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using OneScript.Contexts; +using OneScript.Exceptions; +using OneScript.Localization; +using OneScript.Types; +using OneScript.Values; +using ScriptEngine.Machine; +using ScriptEngine.Machine.Contexts; + +namespace OneScript.StandardLibrary.Xml +{ + [ContextClass("ЗаписьXML", "XMLWriter")] + public class XmlWriterImpl : AutoContext, IDisposable + { + private TextWriterWithSettings _internalTextWriter; + private XmlTextWriter _writer; + private XmlWriterSettingsImpl _settings = XmlWriterSettingsImpl.Constructor(); + private int _depth; + private readonly Stack> _nsmap = new(); + private StringBuilder _stringBuffer; + + private const string DEFAULT_INDENT_STRING = " "; + + public XmlWriterImpl() + { + Indent = true; + _nsmap.Push(new Dictionary()); + } + + private void EnterScope() + { + ++_depth; + var newMap = _nsmap.Peek().ToDictionary((kv) => kv.Key, (kv) => kv.Value); + _nsmap.Push(newMap); + } + + private void ExitScope() + { + _nsmap.Pop(); + --_depth; + } + + private void CheckIfOpen() + { + if (_writer == null) + throw NotOpenException(); + } + + #region Properties + + [ContextProperty("Отступ","Indent")] + public bool Indent { get; set; } + + [ContextProperty("КонтекстПространствИмен", "NamespaceContext")] + public XmlNamespaceContext NamespaceContext + { + get + { + return new XmlNamespaceContext(_depth, _nsmap.Peek()); + } + } + + [ContextProperty("Параметры", "Settings")] + public XmlWriterSettingsImpl Settings + { + get { return _settings; } + } + + #endregion + + #region Methods + + [ContextMethod("ЗаписатьАтрибут","WriteAttribute")] + public void WriteAttribute(string localName, string valueOrNamespace, string value = null) + { + CheckIfOpen(); + if (value == null) + { + _writer.WriteAttributeString(localName, valueOrNamespace); + } + else + { + _writer.WriteAttributeString(localName, valueOrNamespace, value); + } + } + + [ContextMethod("ЗаписатьБезОбработки","WriteRaw")] + public void WriteRaw(string data) + { + CheckIfOpen(); + _writer.WriteRaw(data); + } + + [ContextMethod("ЗаписатьИнструкциюОбработки","WriteProcessingInstruction")] + public void WriteProcessingInstruction(string name, string text) + { + CheckIfOpen(); + _writer.WriteProcessingInstruction(name, text); + } + + [ContextMethod("ЗаписатьКомментарий","WriteComment")] + public void WriteComment(string text) + { + CheckIfOpen(); + _writer.WriteComment(text); + } + + [ContextMethod("ЗаписатьКонецАтрибута","WriteEndAttribute")] + public void WriteEndAttribute() + { + CheckIfOpen(); + _writer.WriteEndAttribute(); + } + + [ContextMethod("ЗаписатьКонецЭлемента","WriteEndElement")] + public void WriteEndElement() + { + CheckIfOpen(); + _internalTextWriter.TrimEndSlashes = true; + _writer.WriteEndElement(); + _internalTextWriter.TrimEndSlashes = false; + ExitScope(); + } + + [ContextMethod("ЗаписатьНачалоАтрибута","WriteStartAttribute")] + public void WriteStartAttribute(string name, string ns = null) + { + CheckIfOpen(); + if (ns == null) + { + _writer.WriteStartAttribute(name); + } + else + { + _writer.WriteStartAttribute(name, ns); + } + + } + + [ContextMethod("ЗаписатьНачалоЭлемента","WriteStartElement")] + public void WriteStartElement(string name, string ns = null) + { + CheckIfOpen(); + if (ns == null) + { + _writer.WriteStartElement(name); + } + else + { + _writer.WriteStartElement(name, ns); + } + EnterScope(); + } + + [ContextMethod("ЗаписатьОбъявлениеXML","WriteXMLDeclaration")] + public void WriteXMLDeclaration() + { + CheckIfOpen(); + _writer.WriteStartDocument(); + } + + [ContextMethod("ЗаписатьСекциюCDATA","WriteCDATASection")] + public void WriteCDATASection(string data) + { + CheckIfOpen(); + _writer.WriteCData(data); + } + + [ContextMethod("ЗаписатьСоответствиеПространстваИмен","WriteNamespaceMapping")] + public void WriteNamespaceMapping(string prefix, string uri) + { + CheckIfOpen(); + _writer.WriteAttributeString("xmlns", prefix, null, uri); + _nsmap.Peek()[prefix] = uri; + } + + [ContextMethod("ЗаписатьСсылкуНаСущность","WriteEntityReference")] + public void WriteEntityReference(string name) + { + CheckIfOpen(); + _writer.WriteEntityRef(name); + } + + [ContextMethod("ЗаписатьТекст","WriteText")] + public void WriteText(string text) + { + CheckIfOpen(); + _writer.WriteString(text); + } + + [ContextMethod("ЗаписатьТекущий","WriteCurrent")] + public void WriteCurrent(XmlReaderImpl reader) + { + CheckIfOpen(); + var nodeType = reader.NodeType.UnderlyingValue; + switch (nodeType) + { + case XmlNodeType.Element: + CopyElementAndAttributes(reader, this); + break; + case XmlNodeType.XmlDeclaration: + WriteXMLDeclaration(); + break; + case XmlNodeType.DocumentType: + CopyDocumentType(reader, this); + break; + case XmlNodeType.Attribute: + CopyAttribute(reader.Name, reader.Value, reader, this); + break; + case XmlNodeType.EndElement: + WriteEndElement(); + break; + case XmlNodeType.CDATA: + WriteCDATASection(reader.Value); + break; + case XmlNodeType.Text: + WriteText(reader.Value); + break; + case XmlNodeType.Whitespace: + case XmlNodeType.SignificantWhitespace: + WriteText(reader.Value); + break; + case XmlNodeType.Comment: + WriteComment(reader.Value); + break; + case XmlNodeType.EntityReference: + WriteEntityReference(reader.Name); + break; + case XmlNodeType.ProcessingInstruction: + WriteProcessingInstruction(reader.Name, reader.Value); + break; + case XmlNodeType.Entity: + case XmlNodeType.EndEntity: + case XmlNodeType.Document: + case XmlNodeType.DocumentFragment: + case XmlNodeType.Notation: + throw CopyingNotSupportedException(nodeType); + + default: + break; + } + } + + private static void CopyDocumentType(XmlReaderImpl reader, XmlWriterImpl writer) + { + writer.WriteDocumentType(reader.Name, reader.Value); + } + + private static void CopyElementAndAttributes(XmlReaderImpl reader, XmlWriterImpl writer) + { + writer.WriteStartElement(reader.Name); + var attributeCount = reader.AttributeCount(); + if (attributeCount != 0) + { + for (var attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) + { + var attributeName = reader.AttributeName(attributeIndex); + var attributeValue = reader.GetAttribute(ValueFactory.Create(attributeIndex)).ExplicitString(); + CopyAttribute(attributeName, attributeValue, reader, writer); + } + } + } + + private static void CopyAttribute(string attributeNameIn, string attributeValue, XmlReaderImpl reader, XmlWriterImpl writer) + { + var splittedName = splitName(attributeNameIn); + if (!string.IsNullOrEmpty(splittedName.prefix)) + { + var readerNsContext = (XmlNamespaceContext)reader.NamespaceContext; + var writerNsContext = (XmlNamespaceContext)writer.NamespaceContext; + if (splittedName.prefix.Equals("xmlns", StringComparison.Ordinal)) + { + writer.WriteNamespaceMapping(splittedName.localName, attributeValue); + return; + } + else + { + var uri = readerNsContext.LookupNamespaceUri(splittedName.prefix); + if (uri is BslStringValue) + { + var value = uri.ExplicitString(); + var currentPrefix = writerNsContext.LookupPrefix(value); + if (!(currentPrefix is BslStringValue && currentPrefix.ExplicitString().Equals(splittedName.prefix, StringComparison.Ordinal))) + { + writer.WriteNamespaceMapping(splittedName.prefix, value); + } + splittedName.prefix = value; + } + } + } + writer.WriteAttribute(splittedName.localName, splittedName.prefix, attributeValue); + } + + private static (string prefix, string localName) splitName(string nameOrLocalName) + { + var parts = nameOrLocalName.Split(':'); + if (parts.Length > 1) return (parts[0], parts[1]); + if (parts[0].Equals("xmlns", StringComparison.Ordinal)) return (parts[0], ""); + return ("", parts[0]); + } + + [ContextMethod("ЗаписатьТипДокумента","WriteDocumentType")] + public void WriteDocumentType(string name, string varArg2, string varArg3 = null, string varArg4 = null) + { + CheckIfOpen(); + + if (string.IsNullOrWhiteSpace(name)) + throw RuntimeException.InvalidNthArgumentValue(1); + + try + { + if (varArg4 != null) + { + if (string.IsNullOrEmpty(varArg2)) + { + varArg2 = null; + if (string.Empty.Equals(varArg3)) + varArg3 = null; + } + else if (string.IsNullOrEmpty(varArg3)) // (A1,A2,,A4) + throw NoSystemIdException(); + + if (string.Empty.Equals(varArg4)) + varArg4 = null; + + _writer.WriteDocType(name, varArg2, varArg3, varArg4); + } + else if (varArg3 != null) + { + if (string.Empty.Equals(varArg2)) + varArg2 = null; + + if (string.Empty.Equals(varArg3)) + varArg3 = null; + + _writer.WriteDocType(name, null, varArg2, varArg3); + } + else + { + _writer.WriteDocType(name, null, null, varArg2); + } + } + catch (ArgumentException) + { + throw RuntimeException.InvalidNthArgumentValue(1); + } + catch (InvalidOperationException) + { + throw WrongOrderException(); + } + } + + [ContextMethod("НайтиПрефикс","LookupPrefix")] + public IValue LookupPrefix(string uri) + { + CheckIfOpen(); + string prefix = _writer.LookupPrefix(uri); + if (prefix == null) + return ValueFactory.Create(); + return ValueFactory.Create(prefix); + } + + [ContextMethod("Закрыть","Close")] + public IValue Close() + { if (_writer==null) return ValueFactory.Create(String.Empty); @@ -348,180 +382,195 @@ public IValue Close() _writer.Close(); Dispose(); - if (IsOpenForString()) - { - var result = _stringBuffer.ToString(); - _stringBuffer = null; - - return ValueFactory.Create(result); - } - else - { + if (IsOpenForString()) + { + var result = _stringBuffer.ToString(); + _stringBuffer = null; + + return ValueFactory.Create(result); + } + else + { return ValueFactory.Create(String.Empty); - } - } - - private void ApplySettings(BslValue encodingOrSettings) - { - if (encodingOrSettings is XmlWriterSettingsImpl settings) - { - _settings = settings; - } - else if ((encodingOrSettings?.SystemType ?? BasicTypes.String) == BasicTypes.String) - { - _settings = XmlWriterSettingsImpl.Constructor( - encodingOrSettings?.ToString(), - null, - Indent, - false, - DEFAULT_INDENT_STRING); - } - else - { - throw RuntimeException.InvalidArgumentType(nameof(encodingOrSettings)); - } - } - - [ContextMethod("ОткрытьФайл","OpenFile")] - public void OpenFile(string path, BslValue encodingOrSettings = null, IValue addBOM = null) - { - ApplySettings(encodingOrSettings); - var fs = new FileStream(path, FileMode.Create, FileAccess.Write); - var clrSettings = _settings.GetClrSettings(addBOM?.AsBoolean() ?? true); - _internalTextWriter = new TextWriterWithSettings(fs, clrSettings); - _writer = new XmlTextWriter(_internalTextWriter); - SetDefaultOptions(); - } - - [ContextMethod("УстановитьСтроку","SetString")] - public void SetString(BslValue encodingOrSettings = null) - { - ApplySettings(encodingOrSettings); - _stringBuffer = new StringBuilder(); - _internalTextWriter = new TextWriterWithSettings(_stringBuffer, _settings.GetClrSettings()); - _writer = new XmlTextWriter(_internalTextWriter); - SetDefaultOptions(); - } - - private void SetDefaultOptions() - { - if (Settings.Indent) - { - _writer.IndentChar = '\xfeff'; - _writer.Formatting = Formatting.Indented; - _writer.Indentation = 1; - } - } - - #endregion - - private bool IsOpenForString() - { - return _stringBuffer != null; - } - - private sealed class TextWriterWithSettings : TextWriter - { - private readonly TextWriter _baseObject; - private readonly XmlWriterSettings _settings; - - public TextWriterWithSettings(Stream stream, XmlWriterSettings settings) - { - _baseObject = new StreamWriter(stream, settings.Encoding); - _settings = settings; - } - - public TextWriterWithSettings(StringBuilder buffer, XmlWriterSettings settings) - { - _baseObject = new StringWriter(buffer); - _settings = settings; - } - - /// - /// Признак необходимости заменять строку ` /` на `/`. - /// См. https://github.com/EvilBeaver/OneScript/issues/768#issuecomment-397848410 - /// - public bool TrimEndSlashes { get; set; } - - public override Encoding Encoding => _settings.Encoding; - - public override void Write(char value) - { - if (value == '\xfeff') - { - _baseObject.Write(_settings.IndentChars); - } - else - { - _baseObject.Write(value); - } - } - - public override void Write(string value) - { - if (value == " /" && TrimEndSlashes) - { - base.Write("/"); - return; - } - base.Write(value); - } - - // netcore3.1 использует эту перегрузку вместо старой со string - public override void Write(char[] buffer) - { - if (TrimEndSlashes && - buffer != default - && buffer.Length == 3 - && buffer[0] == ' ' - && buffer[1] == '/' - && buffer[2] == '>') - { - Write(buffer, 1, 2); - } - else - { - base.Write(buffer); - } - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _baseObject.Close(); - } - - base.Dispose(disposing); - } - } - - public void Dispose() - { - _writer?.Close(); - _writer = null; - } - - [ScriptConstructor] - public static XmlWriterImpl Create() - { - return new XmlWriterImpl(); - } - - public XmlWriter GetNativeWriter() => _writer; - - public static RuntimeException NotOpenException() - { - return new RuntimeException - ("Приемник данных XML не открыт", - "XML data target is not opened"); - } - public static RuntimeException CopyingNotSupportedException(XmlNodeType nodeType) - { - return new RuntimeException - ($"Копирование узла типа {nodeType} не поддерживается", - $"Copying a node of type {nodeType} is not supported"); - } - - } -} + } + } + + private void ApplySettings(BslValue encodingOrSettings) + { + if (encodingOrSettings is XmlWriterSettingsImpl settings) + { + _settings = settings; + } + else if ((encodingOrSettings?.SystemType ?? BasicTypes.String) == BasicTypes.String) + { + _settings = XmlWriterSettingsImpl.Constructor( + encodingOrSettings?.ToString(), + null, + Indent, + false, + DEFAULT_INDENT_STRING); + } + else + { + throw RuntimeException.InvalidArgumentType(nameof(encodingOrSettings)); + } + } + + [ContextMethod("ОткрытьФайл","OpenFile")] + public void OpenFile(string path, BslValue encodingOrSettings = null, IValue addBOM = null) + { + ApplySettings(encodingOrSettings); + var fs = new FileStream(path, FileMode.Create, FileAccess.Write); + var clrSettings = _settings.GetClrSettings(addBOM?.AsBoolean() ?? true); + _internalTextWriter = new TextWriterWithSettings(fs, clrSettings); + _writer = new XmlTextWriter(_internalTextWriter); + SetDefaultOptions(); + } + + [ContextMethod("УстановитьСтроку","SetString")] + public void SetString(BslValue encodingOrSettings = null) + { + ApplySettings(encodingOrSettings); + _stringBuffer = new StringBuilder(); + _internalTextWriter = new TextWriterWithSettings(_stringBuffer, _settings.GetClrSettings()); + _writer = new XmlTextWriter(_internalTextWriter); + SetDefaultOptions(); + } + + private void SetDefaultOptions() + { + if (Settings.Indent) + { + _writer.IndentChar = '\xfeff'; + _writer.Formatting = Formatting.Indented; + _writer.Indentation = 1; + } + } + + #endregion + + private bool IsOpenForString() + { + return _stringBuffer != null; + } + + private sealed class TextWriterWithSettings : TextWriter + { + private readonly TextWriter _baseObject; + private readonly XmlWriterSettings _settings; + + public TextWriterWithSettings(Stream stream, XmlWriterSettings settings) + { + _baseObject = new StreamWriter(stream, settings.Encoding); + _settings = settings; + } + + public TextWriterWithSettings(StringBuilder buffer, XmlWriterSettings settings) + { + _baseObject = new StringWriter(buffer); + _settings = settings; + } + + /// + /// Признак необходимости заменять строку ` /` на `/`. + /// См. https://github.com/EvilBeaver/OneScript/issues/768#issuecomment-397848410 + /// + public bool TrimEndSlashes { get; set; } + + public override Encoding Encoding => _settings.Encoding; + + public override void Write(char value) + { + if (value == '\xfeff') + { + _baseObject.Write(_settings.IndentChars); + } + else + { + _baseObject.Write(value); + } + } + + public override void Write(string value) + { + if (value == " /" && TrimEndSlashes) + { + base.Write("/"); + return; + } + base.Write(value); + } + + // netcore3.1 использует эту перегрузку вместо старой со string + public override void Write(char[] buffer) + { + if (TrimEndSlashes && + buffer != default + && buffer.Length == 3 + && buffer[0] == ' ' + && buffer[1] == '/' + && buffer[2] == '>') + { + Write(buffer, 1, 2); + } + else + { + base.Write(buffer); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _baseObject.Close(); + } + + base.Dispose(disposing); + } + } + + public void Dispose() + { + _writer?.Close(); + _writer = null; + } + + [ScriptConstructor] + public static XmlWriterImpl Create() + { + return new XmlWriterImpl(); + } + + public XmlWriter GetNativeWriter() => _writer; + + public static RuntimeException WrongOrderException() + { + return new RuntimeException + ("Ошибочный порядок записи XML", + "Wrong order of XML record"); + } + + public static RuntimeException NoSystemIdException() + { + return new RuntimeException + ("Отсутствует системный идентификатор", + "System ID missing"); + } + } + + public static RuntimeException NotOpenException() + { + return new RuntimeException + ("Приемник данных XML не открыт", + "XML data target is not opened"); + } + public static RuntimeException CopyingNotSupportedException(XmlNodeType nodeType) + { + return new RuntimeException + ($"Копирование узла типа {nodeType} не поддерживается", + $"Copying a node of type {nodeType} is not supported"); + } + + } +} diff --git a/tests/xmlwrite.os b/tests/xmlwrite.os index 0d2c426f5..779088bbc 100644 --- a/tests/xmlwrite.os +++ b/tests/xmlwrite.os @@ -47,6 +47,11 @@ ВсеТесты.Добавить("ТестДолжен_ПроверитьЗакрытиеПустого"); ВсеТесты.Добавить("ТестДолжен_ПроверитьТипВозвратаЗакрыть_ИзФайла"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьЗаписьТипаДокумента_4Параметра"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьЗаписьТипаДокумента_3Параметра"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьЗаписьТипаДокумента_4ПараметраСОшибкой"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьЗаписьТипаДокумента_ОшибкаПослеНачала"); + Возврат ВсеТесты; КонецФункции @@ -763,6 +768,84 @@ юТест.ПроверитьРавенство(ЧтениеXML.ТипУзла, ТипУзла, Сообщение); КонецПроцедуры +Процедура ТестДолжен_ПроверитьЗаписьТипаДокумента_4Параметра() Экспорт + Варианты = Новый Массив(); + Варианты.Добавить("А,,,:"); + //Варианты.Добавить("А,B,,:"); + Варианты.Добавить("А,,C,:"); + Варианты.Добавить("А,B,C,:"); + Варианты.Добавить("А,,,D:"); + //Варианты.Добавить("А,B,,D:"); + Варианты.Добавить("А,,C,D:"); + Варианты.Добавить("А,B,C,D:"); + + ЗаписьXML = Новый ЗаписьXML; + Для Каждого Пара Из Варианты Цикл + АргРез = СтрРазделить(Пара,":"); + Арг = СтрРазделить(АргРез[0],","); + + ЗаписьXML.УстановитьСтроку(); + ЗаписьXML.ЗаписатьТипДокумента(Арг[0],Арг[1],Арг[2],Арг[3]); + Результат = ЗаписьXML.Закрыть(); + юТест.ПроверитьРавенство(АргРез[1], Результат, "Ошибка для парамеров "+СтрСоединить(Арг,",")); + КонецЦикла; + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьЗаписьТипаДокумента_3Параметра() Экспорт + Варианты = Новый Массив(); + Варианты.Добавить("А,,:"); + Варианты.Добавить("А,B,:"); + Варианты.Добавить("А,,C:"); + Варианты.Добавить("А,B,C:"); + + ЗаписьXML = Новый ЗаписьXML; + Для Каждого Пара Из Варианты Цикл + АргРез = СтрРазделить(Пара,":"); + Арг = СтрРазделить(АргРез[0],","); + + ЗаписьXML.УстановитьСтроку(); + ЗаписьXML.ЗаписатьТипДокумента(Арг[0],Арг[1],Арг[2]); + Результат = ЗаписьXML.Закрыть(); + юТест.ПроверитьРавенство(АргРез[1], Результат, "Ошибка для парамеров "+СтрСоединить(Арг,",")); + КонецЦикла; + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьЗаписьТипаДокумента_4ПараметраСОшибкой() Экспорт + ЗаписьXML = Новый ЗаписьXML; + ЗаписьXML.УстановитьСтроку(); + + БылоИсключение = Ложь; + Попытка + ЗаписьXML.ЗаписатьТипДокумента("A","B",,"D"); + Исключение + Ошибка = ИнформацияОбОшибке().Описание; + юТест.ПроверитьНеРавенство(Найти(Ошибка,"Отсутствует системный идентификатор"), 0, "Неверный вид ошибки: "+Ошибка); + БылоИсключение = Истина; + КонецПопытки; + ЗаписьXML.Закрыть(); + + юТест.ПроверитьИстину(БылоИсключение, "ЗаписьТипаДокумента_4ПараметраСОшибкой"); +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьЗаписьТипаДокумента_ОшибкаПослеНачала() Экспорт + + ЗаписьXML = Новый ЗаписьXML; + ЗаписьXML.УстановитьСтроку(); + ЗаписьXML.ЗаписатьНачалоЭлемента("a"); + + БылоИсключение = Ложь; + Попытка + ЗаписьXML.ЗаписатьТипДокумента("A","B"); + Исключение + Ошибка = ИнформацияОбОшибке().Описание; + юТест.ПроверитьНеРавенство(Найти(Ошибка,"Ошибочный порядок записи XML"), 0, "Неверный вид ошибки: "+Ошибка); + БылоИсключение = Истина; + КонецПопытки; + ЗаписьXML.Закрыть(); + + юТест.ПроверитьИстину(БылоИсключение, "ЗаписьТипаДокумента_ОшибкаПослеНачала"); Процедура ТестДолжен_ПроверитьЗакрытиеПустого() Экспорт Запись = Новый ЗаписьXML; Результат = Запись.Закрыть();