|
|
@@ -0,0 +1,220 @@
|
|
|
+const BYTE_MIN = 0
|
|
|
+const BYTE_MAX = 0xFF
|
|
|
+
|
|
|
+const CONTROL_LABELS = {
|
|
|
+ 0x00: 'NUL',
|
|
|
+ 0x01: 'SOH',
|
|
|
+ 0x02: 'STX',
|
|
|
+ 0x03: 'ETX',
|
|
|
+ 0x04: 'EOT',
|
|
|
+ 0x05: 'ENQ',
|
|
|
+ 0x06: 'ACK',
|
|
|
+ 0x07: 'BEL',
|
|
|
+ 0x08: 'BS',
|
|
|
+ 0x09: '\\t',
|
|
|
+ 0x0A: '\\n',
|
|
|
+ 0x0B: 'VT',
|
|
|
+ 0x0C: '\\f',
|
|
|
+ 0x0D: '\\r',
|
|
|
+ 0x0E: 'SO',
|
|
|
+ 0x0F: 'SI',
|
|
|
+ 0x10: 'DLE',
|
|
|
+ 0x11: 'DC1',
|
|
|
+ 0x12: 'DC2',
|
|
|
+ 0x13: 'DC3',
|
|
|
+ 0x14: 'DC4',
|
|
|
+ 0x15: 'NAK',
|
|
|
+ 0x16: 'SYN',
|
|
|
+ 0x17: 'ETB',
|
|
|
+ 0x18: 'CAN',
|
|
|
+ 0x19: 'EM',
|
|
|
+ 0x1A: 'SUB',
|
|
|
+ 0x1B: 'ESC',
|
|
|
+ 0x1C: 'FS',
|
|
|
+ 0x1D: 'GS',
|
|
|
+ 0x1E: 'RS',
|
|
|
+ 0x1F: 'US',
|
|
|
+ 0x20: 'SP',
|
|
|
+ 0x7F: 'DEL'
|
|
|
+}
|
|
|
+
|
|
|
+function formatHexByte(value) {
|
|
|
+ return `0x${(Number(value) & BYTE_MAX).toString(16).toUpperCase().padStart(2, '0')}`
|
|
|
+}
|
|
|
+
|
|
|
+function getDefaultRows() {
|
|
|
+ return [
|
|
|
+ { label: '字符', meta: '', value: '--', copyValue: '' },
|
|
|
+ { label: 'HEX', meta: '', value: '--', copyValue: '' },
|
|
|
+ { label: 'DEC', meta: '', value: '--', copyValue: '' }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+function createEmptyResultState() {
|
|
|
+ return {
|
|
|
+ asciiCodeErrorText: '',
|
|
|
+ asciiCodeResultRows: getDefaultRows()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function splitValueTokens(text) {
|
|
|
+ return String(text || '')
|
|
|
+ .trim()
|
|
|
+ .split(/[\s,;,;]+/)
|
|
|
+ .map((token) => token.trim())
|
|
|
+ .filter(Boolean)
|
|
|
+}
|
|
|
+
|
|
|
+function tokenLooksNumeric(token) {
|
|
|
+ return /^0x/i.test(token) || /^\d+$/.test(token)
|
|
|
+}
|
|
|
+
|
|
|
+function tokenIsValidNumeric(token) {
|
|
|
+ return /^0x[0-9a-f]+$/i.test(token) || /^\d+$/.test(token)
|
|
|
+}
|
|
|
+
|
|
|
+function shouldParseAsNumeric(text) {
|
|
|
+ const tokens = splitValueTokens(text)
|
|
|
+ if (!tokens.length) return false
|
|
|
+
|
|
|
+ return tokens.every(tokenLooksNumeric)
|
|
|
+}
|
|
|
+
|
|
|
+function parseNumericToken(token) {
|
|
|
+ if (!tokenIsValidNumeric(token)) {
|
|
|
+ throw new Error('数值格式无效')
|
|
|
+ }
|
|
|
+
|
|
|
+ const value = /^0x/i.test(token)
|
|
|
+ ? parseInt(token.slice(2), 16)
|
|
|
+ : Number(token)
|
|
|
+
|
|
|
+ if (!Number.isInteger(value) || value < BYTE_MIN || value > BYTE_MAX) {
|
|
|
+ throw new Error('数值范围需为 0 - 255')
|
|
|
+ }
|
|
|
+
|
|
|
+ return value
|
|
|
+}
|
|
|
+
|
|
|
+function parseNumericBytes(text) {
|
|
|
+ return splitValueTokens(text).map(parseNumericToken)
|
|
|
+}
|
|
|
+
|
|
|
+function parseTextBytes(text) {
|
|
|
+ const bytes = []
|
|
|
+
|
|
|
+ Array.from(String(text || '')).forEach((char) => {
|
|
|
+ const value = char.codePointAt(0)
|
|
|
+ if (!Number.isInteger(value) || value < BYTE_MIN || value > BYTE_MAX) {
|
|
|
+ throw new Error('字符需在 0 - 255 范围内')
|
|
|
+ }
|
|
|
+ bytes.push(value)
|
|
|
+ })
|
|
|
+
|
|
|
+ return bytes
|
|
|
+}
|
|
|
+
|
|
|
+function formatChar(value) {
|
|
|
+ const byte = Number(value) & BYTE_MAX
|
|
|
+ if (Object.prototype.hasOwnProperty.call(CONTROL_LABELS, byte)) return CONTROL_LABELS[byte]
|
|
|
+ if (byte >= 0x21 && byte <= 0x7E) return String.fromCharCode(byte)
|
|
|
+
|
|
|
+ return `\\x${byte.toString(16).toUpperCase().padStart(2, '0')}`
|
|
|
+}
|
|
|
+
|
|
|
+function formatDisplayText(bytes = []) {
|
|
|
+ if (!bytes.length) return '--'
|
|
|
+ if (bytes.every((byte) => {
|
|
|
+ const value = Number(byte) & BYTE_MAX
|
|
|
+ return value >= 0x21 && value <= 0x7E
|
|
|
+ })) {
|
|
|
+ return bytes.map((byte) => String.fromCharCode(Number(byte) & BYTE_MAX)).join('')
|
|
|
+ }
|
|
|
+
|
|
|
+ return bytes.map(formatChar).join(' ')
|
|
|
+}
|
|
|
+
|
|
|
+function bytesToText(bytes = []) {
|
|
|
+ return bytes.map((byte) => String.fromCharCode(Number(byte) & BYTE_MAX)).join('')
|
|
|
+}
|
|
|
+
|
|
|
+function createResultRows(bytes = []) {
|
|
|
+ if (!bytes.length) return getDefaultRows()
|
|
|
+
|
|
|
+ const hexText = bytes.map(formatHexByte).join(' ')
|
|
|
+ const decText = bytes.map((byte) => String(Number(byte) & BYTE_MAX)).join(' ')
|
|
|
+ const charText = formatDisplayText(bytes)
|
|
|
+
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ copyValue: bytesToText(bytes),
|
|
|
+ label: '字符',
|
|
|
+ value: charText
|
|
|
+ },
|
|
|
+ {
|
|
|
+ copyValue: hexText,
|
|
|
+ label: 'HEX',
|
|
|
+ value: hexText
|
|
|
+ },
|
|
|
+ {
|
|
|
+ copyValue: decText,
|
|
|
+ label: 'DEC',
|
|
|
+ value: decText
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+function buildState(source = {}) {
|
|
|
+ const inputText = String(source.asciiCodeInputText || '')
|
|
|
+ const trimmedText = inputText.trim()
|
|
|
+
|
|
|
+ if (!trimmedText) {
|
|
|
+ return {
|
|
|
+ asciiCodeInputText: inputText,
|
|
|
+ ...createEmptyResultState()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const numericMode = shouldParseAsNumeric(trimmedText)
|
|
|
+ const bytes = numericMode ? parseNumericBytes(trimmedText) : parseTextBytes(inputText)
|
|
|
+
|
|
|
+ return {
|
|
|
+ asciiCodeErrorText: '',
|
|
|
+ asciiCodeInputText: inputText,
|
|
|
+ asciiCodeResultRows: createResultRows(bytes)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ return {
|
|
|
+ asciiCodeErrorText: error && error.message ? error.message : '转换失败',
|
|
|
+ asciiCodeInputText: inputText,
|
|
|
+ asciiCodeResultRows: getDefaultRows()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function createInitialState() {
|
|
|
+ return buildState({
|
|
|
+ asciiCodeInputText: ''
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function updateState(state, changedData = {}) {
|
|
|
+ return buildState({
|
|
|
+ ...state,
|
|
|
+ ...changedData
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function clearInput(state = {}) {
|
|
|
+ return updateState(state, {
|
|
|
+ asciiCodeInputText: ''
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = {
|
|
|
+ clearInput,
|
|
|
+ createInitialState,
|
|
|
+ formatHexByte,
|
|
|
+ updateState
|
|
|
+}
|